@thegitai/cli 1.0.0-preview.34 → 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
|
@@ -148,6 +148,17 @@ export const SLASH_COMMANDS = [
|
|
|
148
148
|
description: 'Quit the current session',
|
|
149
149
|
},
|
|
150
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
|
+
}
|
|
151
162
|
function getShellWidth(columns) {
|
|
152
163
|
const safeColumns = Math.max(columns, 20);
|
|
153
164
|
const targetWidth = Math.floor(safeColumns * TUI_WIDTH_RATIO);
|
|
@@ -1414,6 +1425,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1414
1425
|
let latestUsageSummary = null;
|
|
1415
1426
|
let pendingTurnEntries = [];
|
|
1416
1427
|
let activeTurnAbort = null;
|
|
1428
|
+
let activeTurnImageAttachments = [];
|
|
1417
1429
|
let activeServerTurnId = null;
|
|
1418
1430
|
let unacknowledged = new Map();
|
|
1419
1431
|
const blockedRows = new WeakMap();
|
|
@@ -1675,6 +1687,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1675
1687
|
activeTurnAbort = null;
|
|
1676
1688
|
cancelActiveCommand();
|
|
1677
1689
|
const queued = store.getState().queuedMessage;
|
|
1690
|
+
const restoredAttachments = composerAttachmentsAfterCancel(queued ? queued.imageAttachments : [], activeTurnImageAttachments);
|
|
1691
|
+
activeTurnImageAttachments = [];
|
|
1678
1692
|
const cancelledEntries = [
|
|
1679
1693
|
...takePendingTurnEntries(),
|
|
1680
1694
|
{
|
|
@@ -1692,7 +1706,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
1692
1706
|
busySince: null,
|
|
1693
1707
|
commandLog: [],
|
|
1694
1708
|
cursor: queued ? queued.body.length : current.cursor,
|
|
1695
|
-
imageAttachments:
|
|
1709
|
+
imageAttachments: restoredAttachments,
|
|
1696
1710
|
input: queued ? queued.body : current.input,
|
|
1697
1711
|
pastedChunks: queued ? queued.pastedChunks : current.pastedChunks,
|
|
1698
1712
|
promptHistoryCursor: queued ? null : current.promptHistoryCursor,
|
|
@@ -2673,6 +2687,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2673
2687
|
pendingTurnEntries = [];
|
|
2674
2688
|
queueTurnEntry(userEntry);
|
|
2675
2689
|
resetThinkingPacer();
|
|
2690
|
+
activeTurnImageAttachments = imageAttachments;
|
|
2676
2691
|
store.update((current) => ({
|
|
2677
2692
|
...current,
|
|
2678
2693
|
activeTurnInput: input,
|
|
@@ -2726,6 +2741,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2726
2741
|
if (!(await saveActiveSession()))
|
|
2727
2742
|
return;
|
|
2728
2743
|
syncShellStateFromSession();
|
|
2744
|
+
activeTurnImageAttachments = [];
|
|
2729
2745
|
store.update((current) => ({
|
|
2730
2746
|
...current,
|
|
2731
2747
|
activeTurnInput: '',
|
|
@@ -2779,6 +2795,8 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2779
2795
|
const cancelled = isTurnCancelledError(error);
|
|
2780
2796
|
if (exitForAuthenticationError(error))
|
|
2781
2797
|
return;
|
|
2798
|
+
const failedTurnAttachments = activeTurnImageAttachments;
|
|
2799
|
+
activeTurnImageAttachments = [];
|
|
2782
2800
|
store.update((current) => ({
|
|
2783
2801
|
...current,
|
|
2784
2802
|
activeTurnInput: '',
|
|
@@ -2787,7 +2805,7 @@ export async function runClientInteractive({ appendPromptHistory, authConfig, de
|
|
|
2787
2805
|
busyPausedAt: null,
|
|
2788
2806
|
busySince: null,
|
|
2789
2807
|
commandLog: [],
|
|
2790
|
-
imageAttachments:
|
|
2808
|
+
imageAttachments: failedTurnAttachments,
|
|
2791
2809
|
status: cancelled ? 'Ready' : 'Turn failed',
|
|
2792
2810
|
thinkingTitle: '',
|
|
2793
2811
|
thinkingNotes: [],
|
|
@@ -41,6 +41,48 @@ const MODEL_PICKER_BORDER_COLOR = 'cyan';
|
|
|
41
41
|
const MODEL_PICKER_ACCENT_COLOR = 'cyan';
|
|
42
42
|
const MODEL_PICKER_HIGHLIGHT_BG = 'ansi256(87)';
|
|
43
43
|
const MODEL_PICKER_META_INDENT = ' ';
|
|
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;
|
|
51
|
+
}
|
|
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
|
+
};
|
|
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
|
+
}
|
|
75
|
+
};
|
|
76
|
+
if (cursorCol === null) {
|
|
77
|
+
flushTo(text.length);
|
|
78
|
+
return spans;
|
|
79
|
+
}
|
|
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;
|
|
85
|
+
}
|
|
44
86
|
function buildComposerInputLines(input, cursor, promptLabel, placeholder, width) {
|
|
45
87
|
if (!input) {
|
|
46
88
|
return [
|
|
@@ -51,20 +93,22 @@ function buildComposerInputLines(input, cursor, promptLabel, placeholder, width)
|
|
|
51
93
|
const inputWidth = Math.max(1, width - labelWidth);
|
|
52
94
|
const { cursorCol, cursorRow, rows } = splitComposerInput(input, cursor, inputWidth);
|
|
53
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(''));
|
|
54
103
|
return rows
|
|
55
104
|
.slice(firstRow, firstRow + COMPOSER_INPUT_MAX_ROWS)
|
|
56
105
|
.map((text, index) => {
|
|
57
106
|
const absoluteRow = firstRow + index;
|
|
107
|
+
const rowStart = rowOffsets[absoluteRow] ?? 0;
|
|
58
108
|
const label = index === 0
|
|
59
109
|
? span(promptLabel, { color: 'cyan' })
|
|
60
110
|
: span(' '.repeat(labelWidth));
|
|
61
|
-
|
|
62
|
-
return line(label, span(text));
|
|
63
|
-
}
|
|
64
|
-
const before = text.slice(0, cursorCol);
|
|
65
|
-
const cursorChar = text[cursorCol] ?? ' ';
|
|
66
|
-
const after = text.slice(cursorCol + 1);
|
|
67
|
-
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));
|
|
68
112
|
});
|
|
69
113
|
}
|
|
70
114
|
function getEntryColor(kind) {
|
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": {
|