@thegitai/cli 1.0.0-preview.33 → 1.0.0-preview.35
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/src/api/chat.js
CHANGED
|
@@ -7,6 +7,7 @@ import { isUserInputQuestionArray } from './contracts.js';
|
|
|
7
7
|
import { createTraceContext, gatewayFailureCategory, normalizeServerUrl, readErrorResponse, } from './http.js';
|
|
8
8
|
import { collectClientEnvironment } from '../client-environment.js';
|
|
9
9
|
import { collectProjectOrientation } from '../project-orientation.js';
|
|
10
|
+
import { describeSessionImageStore } from '../core/session-image-store.js';
|
|
10
11
|
import { autoAttachImages } from '../core/image-path-extractor.js';
|
|
11
12
|
import { formatTurnFailureMarker } from '../turn-failure-marker.js';
|
|
12
13
|
export class TurnCancelledError extends Error {
|
|
@@ -546,6 +547,7 @@ export async function sendServerUserMessage({ config, session, input, imageAttac
|
|
|
546
547
|
backgroundJobUpdate: backgroundJobUpdate || undefined,
|
|
547
548
|
clientEnvironment: collectClientEnvironment({ env: session.env }),
|
|
548
549
|
projectOrientation: collectProjectOrientation(session.rootDir) ?? undefined,
|
|
550
|
+
sessionImageStore: describeSessionImageStore(session.env) ?? undefined,
|
|
549
551
|
imageAttachments: imageAttachmentsForServer(requestImageAttachments),
|
|
550
552
|
maxToolSteps: session.maxToolSteps,
|
|
551
553
|
autoYes: session.autoYes,
|
|
@@ -111,6 +111,53 @@ export function readSessionImageByIndex(index, env = process.env) {
|
|
|
111
111
|
}
|
|
112
112
|
return null;
|
|
113
113
|
}
|
|
114
|
+
export function describeSessionImageStore(env = process.env) {
|
|
115
|
+
if (!activeSessionId)
|
|
116
|
+
return null;
|
|
117
|
+
const dir = getSessionImageDir(activeSessionId, env);
|
|
118
|
+
if (!existsSync(dir))
|
|
119
|
+
return null;
|
|
120
|
+
const indices = [];
|
|
121
|
+
try {
|
|
122
|
+
for (const name of readdirSync(dir)) {
|
|
123
|
+
const parsed = Number.parseInt(path.basename(name, path.extname(name)), 10);
|
|
124
|
+
if (Number.isInteger(parsed) && parsed >= 1)
|
|
125
|
+
indices.push(parsed);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
if (indices.length === 0)
|
|
132
|
+
return null;
|
|
133
|
+
return { dir, indices: indices.sort((a, b) => a - b) };
|
|
134
|
+
}
|
|
135
|
+
const MAX_STORE_DIR_CHARS = 512;
|
|
136
|
+
const MAX_STORE_INDICES = 200;
|
|
137
|
+
const STORE_CONTROL_CHARS = /[\u0000-\u001f\u007f]/g;
|
|
138
|
+
export function normalizeSessionImageStore(value) {
|
|
139
|
+
if (!value || typeof value !== 'object')
|
|
140
|
+
return null;
|
|
141
|
+
const record = value;
|
|
142
|
+
const dir = String(record.dir ?? '')
|
|
143
|
+
.replace(STORE_CONTROL_CHARS, ' ')
|
|
144
|
+
.trim()
|
|
145
|
+
.slice(0, MAX_STORE_DIR_CHARS);
|
|
146
|
+
if (!dir)
|
|
147
|
+
return null;
|
|
148
|
+
const seen = new Set();
|
|
149
|
+
for (const raw of Array.isArray(record.indices) ? record.indices : []) {
|
|
150
|
+
const parsed = Number(raw);
|
|
151
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
152
|
+
continue;
|
|
153
|
+
seen.add(parsed);
|
|
154
|
+
if (seen.size >= MAX_STORE_INDICES)
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (seen.size === 0)
|
|
158
|
+
return null;
|
|
159
|
+
return { dir, indices: [...seen].sort((a, b) => a - b) };
|
|
160
|
+
}
|
|
114
161
|
export class SessionImageError extends Error {
|
|
115
162
|
code;
|
|
116
163
|
constructor(message, code) {
|
package/dist/src/ui/repl.js
CHANGED
|
@@ -4,6 +4,7 @@ import { approvalScrollLimit, buildTuiFrame, formatJobElapsed, formatTodoProgres
|
|
|
4
4
|
import { createTerminalTitleController } from './tui/terminal-title.js';
|
|
5
5
|
import { captureTerminalWrites, releaseTerminalWrites, } from './tui/terminal-writes.js';
|
|
6
6
|
export { getSlashCommandSuggestions } from './tui/build-frame.js';
|
|
7
|
+
import { composerInputWidth, frameContentWidth } from './tui/composer-layout.js';
|
|
7
8
|
import { agentModeLabel, nextAgentMode, } from '../agent-mode.js';
|
|
8
9
|
import { chat, models } from '../api/index.js';
|
|
9
10
|
import { isTurnCancelledError } from '../api/chat.js';
|
|
@@ -147,6 +148,17 @@ export const SLASH_COMMANDS = [
|
|
|
147
148
|
description: 'Quit the current session',
|
|
148
149
|
},
|
|
149
150
|
];
|
|
151
|
+
export function composerAttachmentsAfterCancel(queuedAttachments, inFlightAttachments) {
|
|
152
|
+
const seen = new Set();
|
|
153
|
+
const kept = [];
|
|
154
|
+
for (const attachment of [...queuedAttachments, ...inFlightAttachments]) {
|
|
155
|
+
if (seen.has(attachment.index))
|
|
156
|
+
continue;
|
|
157
|
+
seen.add(attachment.index);
|
|
158
|
+
kept.push(attachment);
|
|
159
|
+
}
|
|
160
|
+
return kept;
|
|
161
|
+
}
|
|
150
162
|
function getShellWidth(columns) {
|
|
151
163
|
const safeColumns = Math.max(columns, 20);
|
|
152
164
|
const targetWidth = Math.floor(safeColumns * TUI_WIDTH_RATIO);
|
|
@@ -1413,6 +1425,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1413
1425
|
let latestUsageSummary = null;
|
|
1414
1426
|
let pendingTurnEntries = [];
|
|
1415
1427
|
let activeTurnAbort = null;
|
|
1428
|
+
let activeTurnImageAttachments = [];
|
|
1416
1429
|
let activeServerTurnId = null;
|
|
1417
1430
|
let unacknowledged = new Map();
|
|
1418
1431
|
const blockedRows = new WeakMap();
|
|
@@ -1674,6 +1687,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1674
1687
|
activeTurnAbort = null;
|
|
1675
1688
|
cancelActiveCommand();
|
|
1676
1689
|
const queued = store.getState().queuedMessage;
|
|
1690
|
+
const restoredAttachments = composerAttachmentsAfterCancel(queued ? queued.imageAttachments : [], activeTurnImageAttachments);
|
|
1691
|
+
activeTurnImageAttachments = [];
|
|
1677
1692
|
const cancelledEntries = [
|
|
1678
1693
|
...takePendingTurnEntries(),
|
|
1679
1694
|
{
|
|
@@ -1691,7 +1706,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1691
1706
|
busySince: null,
|
|
1692
1707
|
commandLog: [],
|
|
1693
1708
|
cursor: queued ? queued.body.length : current.cursor,
|
|
1694
|
-
imageAttachments:
|
|
1709
|
+
imageAttachments: restoredAttachments,
|
|
1695
1710
|
input: queued ? queued.body : current.input,
|
|
1696
1711
|
pastedChunks: queued ? queued.pastedChunks : current.pastedChunks,
|
|
1697
1712
|
promptHistoryCursor: queued ? null : current.promptHistoryCursor,
|
|
@@ -2672,6 +2687,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2672
2687
|
pendingTurnEntries = [];
|
|
2673
2688
|
queueTurnEntry(userEntry);
|
|
2674
2689
|
resetThinkingPacer();
|
|
2690
|
+
activeTurnImageAttachments = imageAttachments;
|
|
2675
2691
|
store.update((current) => ({
|
|
2676
2692
|
...current,
|
|
2677
2693
|
activeTurnInput: input,
|
|
@@ -2725,6 +2741,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2725
2741
|
if (!(await saveActiveSession()))
|
|
2726
2742
|
return;
|
|
2727
2743
|
syncShellStateFromSession();
|
|
2744
|
+
activeTurnImageAttachments = [];
|
|
2728
2745
|
store.update((current) => ({
|
|
2729
2746
|
...current,
|
|
2730
2747
|
activeTurnInput: '',
|
|
@@ -2778,6 +2795,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2778
2795
|
const cancelled = isTurnCancelledError(error);
|
|
2779
2796
|
if (exitForAuthenticationError(error))
|
|
2780
2797
|
return;
|
|
2798
|
+
const failedTurnAttachments = activeTurnImageAttachments;
|
|
2799
|
+
activeTurnImageAttachments = [];
|
|
2781
2800
|
store.update((current) => ({
|
|
2782
2801
|
...current,
|
|
2783
2802
|
activeTurnInput: '',
|
|
@@ -2786,7 +2805,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2786
2805
|
busyPausedAt: null,
|
|
2787
2806
|
busySince: null,
|
|
2788
2807
|
commandLog: [],
|
|
2789
|
-
imageAttachments:
|
|
2808
|
+
imageAttachments: failedTurnAttachments,
|
|
2790
2809
|
status: cancelled ? 'Ready' : 'Turn failed',
|
|
2791
2810
|
thinkingTitle: '',
|
|
2792
2811
|
thinkingNotes: [],
|
|
@@ -2951,11 +2970,12 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2951
2970
|
};
|
|
2952
2971
|
const shellInputHandlers = {
|
|
2953
2972
|
getApprovalScrollLimit: () => {
|
|
2954
|
-
const contentWidth =
|
|
2973
|
+
const contentWidth = frameContentWidth(terminalCols);
|
|
2955
2974
|
return approvalScrollLimit(store.getState().approvalPrompt, contentWidth, terminalRows);
|
|
2956
2975
|
},
|
|
2976
|
+
getComposerInputWidth: () => composerInputWidth(terminalCols, store.getState()),
|
|
2957
2977
|
getTranscriptScrollLimit: () => {
|
|
2958
|
-
const contentWidth =
|
|
2978
|
+
const contentWidth = frameContentWidth(terminalCols);
|
|
2959
2979
|
const blocks = store.getState().transcript.map((entry) => renderTranscriptEntryLines(entry, contentWidth));
|
|
2960
2980
|
return blocks.reduce((total, block, index) => total + block.length + (index > 0 ? 1 : 0), 0);
|
|
2961
2981
|
},
|
|
@@ -5,6 +5,7 @@ import { renderFormattedBodyLines, renderPreformattedBodyLines, } from './markdo
|
|
|
5
5
|
import { displayWidth, line, padToWidth, plainLine, sliceToWidth, span, wrapText, } from './text.js';
|
|
6
6
|
import { isDarkTerminalBackground, mutedColor, mutedStyle } from './terminal-theme.js';
|
|
7
7
|
import { buildUserInputOverlayLines, } from './user-input.js';
|
|
8
|
+
import { composerPromptLabel, frameContentWidth, splitComposerInput, } from './composer-layout.js';
|
|
8
9
|
const WORKING_CLOCK_ICON = '◷';
|
|
9
10
|
const BRAILLE_SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴'];
|
|
10
11
|
const TODO_PANEL_MAX_ROWS = 12;
|
|
@@ -40,50 +41,47 @@ const MODEL_PICKER_BORDER_COLOR = 'cyan';
|
|
|
40
41
|
const MODEL_PICKER_ACCENT_COLOR = 'cyan';
|
|
41
42
|
const MODEL_PICKER_HIGHLIGHT_BG = 'ansi256(87)';
|
|
42
43
|
const MODEL_PICKER_META_INDENT = ' ';
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const captureCursor = () => {
|
|
51
|
-
if (capturedCursor)
|
|
52
|
-
return;
|
|
53
|
-
const current = rows[rows.length - 1] ?? '';
|
|
54
|
-
if (current.length >= safeWidth) {
|
|
55
|
-
rows.push('');
|
|
56
|
-
cursorRow = rows.length - 1;
|
|
57
|
-
cursorCol = 0;
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
cursorRow = rows.length - 1;
|
|
61
|
-
cursorCol = current.length;
|
|
44
|
+
const COMPOSER_MARKER_PATTERN = /\[Image #\d+\]|\[Pasted Text: [^\]\n]*\]/g;
|
|
45
|
+
function composerMarkerMask(text) {
|
|
46
|
+
const mask = new Array(text.length).fill(false);
|
|
47
|
+
for (const match of text.matchAll(COMPOSER_MARKER_PATTERN)) {
|
|
48
|
+
const start = match.index ?? 0;
|
|
49
|
+
for (let offset = start; offset < start + match[0].length; offset += 1) {
|
|
50
|
+
mask[offset] = true;
|
|
62
51
|
}
|
|
63
|
-
|
|
52
|
+
}
|
|
53
|
+
return mask;
|
|
54
|
+
}
|
|
55
|
+
function composerRowSpans(text, mask, cursorCol) {
|
|
56
|
+
const spans = [];
|
|
57
|
+
const push = (value, marker, cursor) => {
|
|
58
|
+
if (!value)
|
|
59
|
+
return;
|
|
60
|
+
spans.push(span(value, {
|
|
61
|
+
...(marker ? { color: 'cyan' } : {}),
|
|
62
|
+
...(cursor ? { inverse: true } : {}),
|
|
63
|
+
}));
|
|
64
64
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
let cut = 0;
|
|
66
|
+
const flushTo = (end) => {
|
|
67
|
+
while (cut < end) {
|
|
68
|
+
const marker = mask[cut] ?? false;
|
|
69
|
+
let runEnd = cut + 1;
|
|
70
|
+
while (runEnd < end && (mask[runEnd] ?? false) === marker)
|
|
71
|
+
runEnd += 1;
|
|
72
|
+
push(text.slice(cut, runEnd), marker, false);
|
|
73
|
+
cut = runEnd;
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
rows[rows.length - 1] = `${rows[rows.length - 1] ?? ''}${char}`;
|
|
82
|
-
}
|
|
83
|
-
if (!capturedCursor) {
|
|
84
|
-
captureCursor();
|
|
75
|
+
};
|
|
76
|
+
if (cursorCol === null) {
|
|
77
|
+
flushTo(text.length);
|
|
78
|
+
return spans;
|
|
85
79
|
}
|
|
86
|
-
|
|
80
|
+
flushTo(Math.min(cursorCol, text.length));
|
|
81
|
+
push(text[cursorCol] ?? ' ', mask[cursorCol] ?? false, true);
|
|
82
|
+
cut = Math.min(cursorCol + 1, text.length);
|
|
83
|
+
flushTo(text.length);
|
|
84
|
+
return spans;
|
|
87
85
|
}
|
|
88
86
|
function buildComposerInputLines(input, cursor, promptLabel, placeholder, width) {
|
|
89
87
|
if (!input) {
|
|
@@ -95,20 +93,22 @@ function buildComposerInputLines(input, cursor, promptLabel, placeholder, width)
|
|
|
95
93
|
const inputWidth = Math.max(1, width - labelWidth);
|
|
96
94
|
const { cursorCol, cursorRow, rows } = splitComposerInput(input, cursor, inputWidth);
|
|
97
95
|
const firstRow = Math.min(Math.max(cursorRow - COMPOSER_INPUT_MAX_ROWS + 1, 0), Math.max(rows.length - COMPOSER_INPUT_MAX_ROWS, 0));
|
|
96
|
+
const rowOffsets = [];
|
|
97
|
+
let consumed = 0;
|
|
98
|
+
for (const row of rows) {
|
|
99
|
+
rowOffsets.push(consumed);
|
|
100
|
+
consumed += row.length;
|
|
101
|
+
}
|
|
102
|
+
const mask = composerMarkerMask(rows.join(''));
|
|
98
103
|
return rows
|
|
99
104
|
.slice(firstRow, firstRow + COMPOSER_INPUT_MAX_ROWS)
|
|
100
105
|
.map((text, index) => {
|
|
101
106
|
const absoluteRow = firstRow + index;
|
|
107
|
+
const rowStart = rowOffsets[absoluteRow] ?? 0;
|
|
102
108
|
const label = index === 0
|
|
103
109
|
? span(promptLabel, { color: 'cyan' })
|
|
104
110
|
: span(' '.repeat(labelWidth));
|
|
105
|
-
|
|
106
|
-
return line(label, span(text));
|
|
107
|
-
}
|
|
108
|
-
const before = text.slice(0, cursorCol);
|
|
109
|
-
const cursorChar = text[cursorCol] ?? ' ';
|
|
110
|
-
const after = text.slice(cursorCol + 1);
|
|
111
|
-
return line(label, span(before), span(cursorChar, { inverse: true }), span(after));
|
|
111
|
+
return line(label, ...composerRowSpans(text, mask.slice(rowStart, rowStart + text.length), absoluteRow === cursorRow ? cursorCol : null));
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
function getEntryColor(kind) {
|
|
@@ -1190,7 +1190,7 @@ function countSectionLines(sections) {
|
|
|
1190
1190
|
return sections.reduce((sum, section) => sum + section.lines.length, 0);
|
|
1191
1191
|
}
|
|
1192
1192
|
export function userInputViewportForFrame(state, cols, rows, spinnerFrame, elapsedSeconds, nowMs = 0) {
|
|
1193
|
-
const contentWidth =
|
|
1193
|
+
const contentWidth = frameContentWidth(cols);
|
|
1194
1194
|
const liveRows = buildLiveLines(state, contentWidth, spinnerFrame, elapsedSeconds, nowMs).length;
|
|
1195
1195
|
const overlayHeight = Math.max(0, rows - liveRows - composerFooterLines(state).length);
|
|
1196
1196
|
return {
|
|
@@ -1208,7 +1208,7 @@ function sliceTranscriptLines(lines, maxLines, scrollOffset) {
|
|
|
1208
1208
|
return lines.slice(start, start + maxLines);
|
|
1209
1209
|
}
|
|
1210
1210
|
export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, nowMs = 0) {
|
|
1211
|
-
const contentWidth =
|
|
1211
|
+
const contentWidth = frameContentWidth(cols);
|
|
1212
1212
|
const gutter = Math.max(Math.floor((cols - contentWidth) / 2), 0);
|
|
1213
1213
|
const buildTranscriptLines = (wrapWidth) => {
|
|
1214
1214
|
const blocks = state.transcript.map((entry) => cachedTranscriptEntryLines(entry, wrapWidth));
|
|
@@ -1240,7 +1240,7 @@ export function buildTuiFrame(state, cols, rows, spinnerFrame, elapsedSeconds, n
|
|
|
1240
1240
|
: ' sends it to the agent now', { color: 'gray' }), span(' · ↑', { color: 'cyan', bold: true }), span(' edit', { color: 'gray' }), span(' · Esc', { color: 'cyan', bold: true }), span(' discard', { color: 'gray' })));
|
|
1241
1241
|
}
|
|
1242
1242
|
else {
|
|
1243
|
-
const promptLabel = state
|
|
1243
|
+
const promptLabel = composerPromptLabel(state);
|
|
1244
1244
|
const placeholder = state.busy
|
|
1245
1245
|
? 'Type to queue the next message'
|
|
1246
1246
|
: 'Type a request or /help';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const COMPOSER_PROMPT_LABEL_IDLE = '❯ ';
|
|
2
|
+
export const COMPOSER_PROMPT_LABEL_BUSY = 'queue> ';
|
|
3
|
+
export function composerPromptLabel(state) {
|
|
4
|
+
return state.busy ? COMPOSER_PROMPT_LABEL_BUSY : COMPOSER_PROMPT_LABEL_IDLE;
|
|
5
|
+
}
|
|
6
|
+
export function frameContentWidth(cols) {
|
|
7
|
+
return Math.max(20, Math.floor(cols * 0.95) - 2);
|
|
8
|
+
}
|
|
9
|
+
export function composerInputWidth(cols, state) {
|
|
10
|
+
return Math.max(1, frameContentWidth(cols) - composerPromptLabel(state).length);
|
|
11
|
+
}
|
|
12
|
+
function buildComposerLayout(input, cursor, width) {
|
|
13
|
+
const safeWidth = Math.max(1, width);
|
|
14
|
+
const normalizedCursor = Math.min(Math.max(cursor, 0), input.length);
|
|
15
|
+
const rows = [{ cursorOffsets: [0], text: '' }];
|
|
16
|
+
let cursorRow = 0;
|
|
17
|
+
let cursorCol = 0;
|
|
18
|
+
let capturedCursor = false;
|
|
19
|
+
const currentRow = () => rows[rows.length - 1];
|
|
20
|
+
const addRow = (offset) => {
|
|
21
|
+
rows.push({ cursorOffsets: [offset], text: '' });
|
|
22
|
+
};
|
|
23
|
+
const captureCursor = () => {
|
|
24
|
+
if (capturedCursor)
|
|
25
|
+
return;
|
|
26
|
+
if (currentRow().text.length >= safeWidth) {
|
|
27
|
+
addRow(normalizedCursor);
|
|
28
|
+
}
|
|
29
|
+
cursorRow = rows.length - 1;
|
|
30
|
+
cursorCol = currentRow().text.length;
|
|
31
|
+
capturedCursor = true;
|
|
32
|
+
};
|
|
33
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
34
|
+
const char = input[index] ?? '';
|
|
35
|
+
if (char !== '\n' &&
|
|
36
|
+
char !== '\r' &&
|
|
37
|
+
currentRow().text.length >= safeWidth) {
|
|
38
|
+
addRow(index);
|
|
39
|
+
}
|
|
40
|
+
if (index === normalizedCursor) {
|
|
41
|
+
captureCursor();
|
|
42
|
+
}
|
|
43
|
+
if (char === '\r') {
|
|
44
|
+
currentRow().cursorOffsets[currentRow().text.length] = index + 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (char === '\n') {
|
|
48
|
+
currentRow().cursorOffsets[currentRow().text.length] =
|
|
49
|
+
input[index - 1] === '\r' ? index - 1 : index;
|
|
50
|
+
addRow(index + 1);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
currentRow().text += char;
|
|
54
|
+
currentRow().cursorOffsets.push(index + 1);
|
|
55
|
+
}
|
|
56
|
+
if (!capturedCursor) {
|
|
57
|
+
captureCursor();
|
|
58
|
+
}
|
|
59
|
+
return { cursorCol, cursorRow, rows };
|
|
60
|
+
}
|
|
61
|
+
export function splitComposerInput(input, cursor, width) {
|
|
62
|
+
const layout = buildComposerLayout(input, cursor, width);
|
|
63
|
+
return {
|
|
64
|
+
cursorCol: layout.cursorCol,
|
|
65
|
+
cursorRow: layout.cursorRow,
|
|
66
|
+
rows: layout.rows.map((row) => row.text),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function moveComposerCursorVertically(input, cursor, width, direction) {
|
|
70
|
+
const layout = buildComposerLayout(input, cursor, width);
|
|
71
|
+
const targetRow = layout.rows[layout.cursorRow + direction];
|
|
72
|
+
if (!targetRow)
|
|
73
|
+
return null;
|
|
74
|
+
const targetCol = Math.min(layout.cursorCol, targetRow.text.length);
|
|
75
|
+
return targetRow.cursorOffsets[targetCol] ?? null;
|
|
76
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { moveComposerCursorVertically } from './composer-layout.js';
|
|
1
2
|
import { readClipboardImage, readClipboardText } from '../../core/clipboard.js';
|
|
2
3
|
import { MAX_IMAGES_PER_MESSAGE, MAX_TOTAL_IMAGE_BYTES_PER_MESSAGE, approximateBase64DecodedBytes, totalAttachmentBytes, } from '../../core/image-limits.js';
|
|
3
4
|
import { tryCacheAttachmentBytes } from '../../core/session-image-store.js';
|
|
@@ -479,9 +480,13 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
479
480
|
});
|
|
480
481
|
return;
|
|
481
482
|
}
|
|
482
|
-
|
|
483
|
+
const nextCursor = moveComposerCursorVertically(state.input, state.cursor, handlers.getComposerInputWidth?.() ?? Number.POSITIVE_INFINITY, -1);
|
|
484
|
+
if (nextCursor === null && state.busy)
|
|
483
485
|
return;
|
|
484
486
|
store.update((current) => {
|
|
487
|
+
if (nextCursor !== null) {
|
|
488
|
+
return { ...current, commandCursor: 0, cursor: nextCursor };
|
|
489
|
+
}
|
|
485
490
|
const next = navigatePromptHistory(current, 'previous');
|
|
486
491
|
prepareForComposerInputChange(current, next.input);
|
|
487
492
|
return next;
|
|
@@ -489,9 +494,13 @@ export function handleShellKeyEvent(store, handlers, event) {
|
|
|
489
494
|
return;
|
|
490
495
|
}
|
|
491
496
|
if (key.downArrow) {
|
|
492
|
-
|
|
497
|
+
const nextCursor = moveComposerCursorVertically(state.input, state.cursor, handlers.getComposerInputWidth?.() ?? Number.POSITIVE_INFINITY, 1);
|
|
498
|
+
if (nextCursor === null && state.busy)
|
|
493
499
|
return;
|
|
494
500
|
store.update((current) => {
|
|
501
|
+
if (nextCursor !== null) {
|
|
502
|
+
return { ...current, commandCursor: 0, cursor: nextCursor };
|
|
503
|
+
}
|
|
495
504
|
const next = navigatePromptHistory(current, 'next');
|
|
496
505
|
prepareForComposerInputChange(current, next.input);
|
|
497
506
|
return next;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thegitai/cli",
|
|
3
|
-
"version": "1.0.0-preview.
|
|
3
|
+
"version": "1.0.0-preview.35",
|
|
4
4
|
"description": "TheGitAI is an agentic AI coding tool for your terminal. It reads and searches your repository, writes and edits files, runs your tests, and verifies the change before handing it back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentic-ai",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"@lydell/node-pty-linux-x64": "1.1.0",
|
|
45
45
|
"@lydell/node-pty-win32-arm64": "1.1.0",
|
|
46
46
|
"@lydell/node-pty-win32-x64": "1.1.0",
|
|
47
|
-
"@thegitai/tui-darwin-arm64": "1.0.0-preview.
|
|
48
|
-
"@thegitai/tui-darwin-x64": "1.0.0-preview.
|
|
49
|
-
"@thegitai/tui-linux-arm64": "1.0.0-preview.
|
|
50
|
-
"@thegitai/tui-linux-x64": "1.0.0-preview.
|
|
51
|
-
"@thegitai/tui-win32-x64": "1.0.0-preview.
|
|
47
|
+
"@thegitai/tui-darwin-arm64": "1.0.0-preview.35",
|
|
48
|
+
"@thegitai/tui-darwin-x64": "1.0.0-preview.35",
|
|
49
|
+
"@thegitai/tui-linux-arm64": "1.0.0-preview.35",
|
|
50
|
+
"@thegitai/tui-linux-x64": "1.0.0-preview.35",
|
|
51
|
+
"@thegitai/tui-win32-x64": "1.0.0-preview.35",
|
|
52
52
|
"@vscode/ripgrep": "1.18.0"
|
|
53
53
|
},
|
|
54
54
|
"repository": {
|