praxis-agent 0.21.3 → 0.21.4
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.
package/dist/cli/interactive.js
CHANGED
|
@@ -16,6 +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
20
|
import { loadGitDiff, visiblePatchLines, } from './tui/git-diff.js';
|
|
20
21
|
import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
|
|
21
22
|
import { createRecentlyDeniedStore, } from './tui/recently-denied.js';
|
|
@@ -447,6 +448,13 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
447
448
|
// conversation content appears.
|
|
448
449
|
const resumed = resume !== undefined && resumedWithTranscript;
|
|
449
450
|
const freshSession = !resumed && !hasConversationHistory;
|
|
451
|
+
// Fullscreen projects only the newest transcript tail that fits the fixed
|
|
452
|
+
// viewport, leaving the composer/status chrome intact and keeping the active
|
|
453
|
+
// stream visible. Classic and screen-reader modes always render the full
|
|
454
|
+
// history exactly as before.
|
|
455
|
+
const projectedHistory = fixedViewport && !axScreenReader
|
|
456
|
+
? projectTranscriptTail(history, Math.max(1, (rows ?? 0) - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS), width)
|
|
457
|
+
: history;
|
|
450
458
|
const sessionLoadRef = useRef(0);
|
|
451
459
|
const [turnDiffs, setTurnDiffs] = useState([]);
|
|
452
460
|
const turnNumberRef = useRef(0);
|
|
@@ -5683,7 +5691,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
5683
5691
|
minHeight: 0,
|
|
5684
5692
|
overflowY: 'hidden',
|
|
5685
5693
|
}
|
|
5686
|
-
: {}), children: _jsx(Transcript, { items:
|
|
5694
|
+
: {}), children: _jsx(Transcript, { items: projectedHistory, activeText: activeText, activeThinking: activeThinking, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }) }), externalEditorRequest !== null ||
|
|
5687
5695
|
keybindingsEditing ||
|
|
5688
5696
|
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 ||
|
|
5689
5697
|
(permissionSelection === 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TranscriptItem } from './claude-style.js';
|
|
2
|
+
/**
|
|
3
|
+
* Rows reserved outside the shrinkable fullscreen transcript region: the top
|
|
4
|
+
* identity chrome, the composer separator/prompt/footer, and the status line,
|
|
5
|
+
* plus a small headroom so the active streaming tail stays visible below the
|
|
6
|
+
* projected history suffix. Fullscreen computes the transcript budget as
|
|
7
|
+
* `rows - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FULLSCREEN_TRANSCRIPT_RESERVED_ROWS = 12;
|
|
10
|
+
/**
|
|
11
|
+
* Compact marker prepended to the projected tail of an oversized newest item so
|
|
12
|
+
* the truncation is visually explicit while the newest lines remain visible.
|
|
13
|
+
*/
|
|
14
|
+
export declare const TRANSCRIPT_TRUNCATION_MARKER = "\u2026";
|
|
15
|
+
/**
|
|
16
|
+
* Deterministic estimate of the terminal rows one transcript item occupies in
|
|
17
|
+
* the TUI presentation. It is intentionally conservative (leans toward
|
|
18
|
+
* overestimation) so the suffix projector keeps the newest content visible
|
|
19
|
+
* rather than dropping items that would actually render.
|
|
20
|
+
*/
|
|
21
|
+
export declare function estimateTranscriptLines(item: TranscriptItem, width: number): number;
|
|
22
|
+
/**
|
|
23
|
+
* Pure, deterministic suffix projector for the fullscreen transcript region.
|
|
24
|
+
*
|
|
25
|
+
* Given transcript items, a terminal row budget, and the usable width, it
|
|
26
|
+
* returns the longest suffix of items whose conservative line estimate fits
|
|
27
|
+
* the budget. The newest user/assistant content is always retained; if even
|
|
28
|
+
* the newest item exceeds the budget, that item is projected as a bounded tail
|
|
29
|
+
* clone rather than rendering an empty transcript (or clipping the tail below
|
|
30
|
+
* the viewport). Ordering and item object identity are preserved for items
|
|
31
|
+
* that fit and the input is never mutated.
|
|
32
|
+
*/
|
|
33
|
+
export declare function projectTranscriptTail(items: readonly TranscriptItem[], budget: number, width: number): readonly TranscriptItem[];
|
|
34
|
+
//# sourceMappingURL=transcript-viewport.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rows reserved outside the shrinkable fullscreen transcript region: the top
|
|
3
|
+
* identity chrome, the composer separator/prompt/footer, and the status line,
|
|
4
|
+
* plus a small headroom so the active streaming tail stays visible below the
|
|
5
|
+
* projected history suffix. Fullscreen computes the transcript budget as
|
|
6
|
+
* `rows - FULLSCREEN_TRANSCRIPT_RESERVED_ROWS`.
|
|
7
|
+
*/
|
|
8
|
+
export const FULLSCREEN_TRANSCRIPT_RESERVED_ROWS = 12;
|
|
9
|
+
/**
|
|
10
|
+
* Conservative width-aware row estimate for a block of text. Blank lines still
|
|
11
|
+
* occupy a row, and every logical line wraps across `Math.ceil(length / width)`
|
|
12
|
+
* rows so the estimate leans high and never drops content that would actually
|
|
13
|
+
* fit.
|
|
14
|
+
*/
|
|
15
|
+
function wrappedLineCount(text, width) {
|
|
16
|
+
if (text === '')
|
|
17
|
+
return 1;
|
|
18
|
+
const usable = Math.max(1, width);
|
|
19
|
+
let rows = 0;
|
|
20
|
+
for (const line of text.split('\n')) {
|
|
21
|
+
rows += Math.max(1, Math.ceil(line.length / usable));
|
|
22
|
+
}
|
|
23
|
+
return rows;
|
|
24
|
+
}
|
|
25
|
+
function bounded(text, max) {
|
|
26
|
+
return text.length > max ? text.slice(0, max) : text;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compact marker prepended to the projected tail of an oversized newest item so
|
|
30
|
+
* the truncation is visually explicit while the newest lines remain visible.
|
|
31
|
+
*/
|
|
32
|
+
export const TRANSCRIPT_TRUNCATION_MARKER = '…';
|
|
33
|
+
/**
|
|
34
|
+
* Projects a text block to the longest trailing suffix (newest content) that
|
|
35
|
+
* fits `budget` terminal rows, prefixed with the truncation marker. The marker
|
|
36
|
+
* merges into the first kept line, so the result is exactly
|
|
37
|
+
* `${TRANSCRIPT_TRUNCATION_MARKER}${suffix}`. A text that already fits is
|
|
38
|
+
* returned unchanged.
|
|
39
|
+
*/
|
|
40
|
+
function projectTextTail(text, budget, width) {
|
|
41
|
+
if (wrappedLineCount(text, width) <= budget)
|
|
42
|
+
return text;
|
|
43
|
+
const lines = text.split('\n');
|
|
44
|
+
let start = lines.length;
|
|
45
|
+
// Wrapped rows of every kept line after the current front line (marker-free).
|
|
46
|
+
let rowsAfter = 0;
|
|
47
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
48
|
+
const line = lines[index];
|
|
49
|
+
if (line === undefined)
|
|
50
|
+
break;
|
|
51
|
+
// This line becomes the front of the tail and carries the marker.
|
|
52
|
+
if (rowsAfter +
|
|
53
|
+
wrappedLineCount(`${TRANSCRIPT_TRUNCATION_MARKER}${line}`, width) >
|
|
54
|
+
budget) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
start = index;
|
|
58
|
+
rowsAfter += wrappedLineCount(line, width);
|
|
59
|
+
}
|
|
60
|
+
return `${TRANSCRIPT_TRUNCATION_MARKER}${lines.slice(start).join('\n')}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns a cloned oversized text-bearing item whose text/summary is a bounded
|
|
64
|
+
* trailing suffix that fits `budget` rows, preserving the newest content and
|
|
65
|
+
* the item kind without mutating the input. Non-text display items (context,
|
|
66
|
+
* tool, tool-result, shell, shell-result) are returned unchanged.
|
|
67
|
+
*/
|
|
68
|
+
function projectOversizedItem(item, budget, width) {
|
|
69
|
+
switch (item.kind) {
|
|
70
|
+
case 'user':
|
|
71
|
+
case 'assistant':
|
|
72
|
+
case 'notice':
|
|
73
|
+
case 'warning':
|
|
74
|
+
case 'local-result':
|
|
75
|
+
return { ...item, text: projectTextTail(item.text, budget, width) };
|
|
76
|
+
case 'thinking':
|
|
77
|
+
return {
|
|
78
|
+
...item,
|
|
79
|
+
text: projectTextTail(item.text, Math.max(1, budget - 1), width),
|
|
80
|
+
};
|
|
81
|
+
case 'compact':
|
|
82
|
+
return {
|
|
83
|
+
...item,
|
|
84
|
+
summary: projectTextTail(item.summary, Math.max(1, budget - 1), width),
|
|
85
|
+
};
|
|
86
|
+
default:
|
|
87
|
+
return item;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Deterministic estimate of the terminal rows one transcript item occupies in
|
|
92
|
+
* the TUI presentation. It is intentionally conservative (leans toward
|
|
93
|
+
* overestimation) so the suffix projector keeps the newest content visible
|
|
94
|
+
* rather than dropping items that would actually render.
|
|
95
|
+
*/
|
|
96
|
+
export function estimateTranscriptLines(item, width) {
|
|
97
|
+
switch (item.kind) {
|
|
98
|
+
case 'user':
|
|
99
|
+
case 'assistant':
|
|
100
|
+
case 'notice':
|
|
101
|
+
case 'warning':
|
|
102
|
+
case 'local-result':
|
|
103
|
+
return wrappedLineCount(item.text, width);
|
|
104
|
+
case 'thinking':
|
|
105
|
+
return 1 + wrappedLineCount(item.text, width);
|
|
106
|
+
case 'compact':
|
|
107
|
+
return 1 + wrappedLineCount(item.summary, width);
|
|
108
|
+
case 'context':
|
|
109
|
+
return 12 + item.skills.length + item.memoryFiles.length;
|
|
110
|
+
case 'tool':
|
|
111
|
+
return 1 + (item.detail ? 1 : 0);
|
|
112
|
+
case 'tool-result':
|
|
113
|
+
return 1 + wrappedLineCount(bounded(item.text, 500), width);
|
|
114
|
+
case 'shell':
|
|
115
|
+
return 2 + wrappedLineCount(item.command, width);
|
|
116
|
+
case 'shell-result':
|
|
117
|
+
return wrappedLineCount(bounded(`${item.stdout}\n${item.stderr}`, 500), width);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Pure, deterministic suffix projector for the fullscreen transcript region.
|
|
122
|
+
*
|
|
123
|
+
* Given transcript items, a terminal row budget, and the usable width, it
|
|
124
|
+
* returns the longest suffix of items whose conservative line estimate fits
|
|
125
|
+
* the budget. The newest user/assistant content is always retained; if even
|
|
126
|
+
* the newest item exceeds the budget, that item is projected as a bounded tail
|
|
127
|
+
* clone rather than rendering an empty transcript (or clipping the tail below
|
|
128
|
+
* the viewport). Ordering and item object identity are preserved for items
|
|
129
|
+
* that fit and the input is never mutated.
|
|
130
|
+
*/
|
|
131
|
+
export function projectTranscriptTail(items, budget, width) {
|
|
132
|
+
if (items.length === 0)
|
|
133
|
+
return [];
|
|
134
|
+
const usableBudget = Math.max(1, budget);
|
|
135
|
+
let rows = 0;
|
|
136
|
+
let start = items.length;
|
|
137
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
138
|
+
const item = items[index];
|
|
139
|
+
if (item === undefined)
|
|
140
|
+
break;
|
|
141
|
+
const estimate = estimateTranscriptLines(item, width);
|
|
142
|
+
if (rows + estimate > usableBudget) {
|
|
143
|
+
// Stop at the first item that no longer fits. When no item has been
|
|
144
|
+
// added yet, even the newest item exceeds the budget: returning it whole
|
|
145
|
+
// would let Ink clip the oversized tail below the viewport, so project a
|
|
146
|
+
// bounded suffix clone that keeps the newest content visible instead.
|
|
147
|
+
return start === items.length
|
|
148
|
+
? [projectOversizedItem(item, usableBudget, width)]
|
|
149
|
+
: items.slice(start);
|
|
150
|
+
}
|
|
151
|
+
rows += estimate;
|
|
152
|
+
start = index;
|
|
153
|
+
}
|
|
154
|
+
return items.slice(start);
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=transcript-viewport.js.map
|