mini-coder 0.6.5 → 0.7.0
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/package.json +1 -1
- package/src/tui-conversation.ts +3 -2
- package/src/tui-overlay.ts +116 -8
- package/src/tui.ts +6 -2
package/package.json
CHANGED
package/src/tui-conversation.ts
CHANGED
|
@@ -50,15 +50,16 @@ function ConversationMessageToolCall(call: TUIToolCall) {
|
|
|
50
50
|
const tail = call.output.trim().slice(-200);
|
|
51
51
|
const blocks: Node[] = [];
|
|
52
52
|
|
|
53
|
-
blocks.push(Text(tail, { fgColor: theme.white, wrap: "word" }));
|
|
54
|
-
|
|
55
53
|
if (tail.length !== call.output.trim().length) {
|
|
56
54
|
blocks.push(
|
|
57
55
|
Text("Showing the last 200 characters", {
|
|
58
56
|
fgColor: theme.bblack,
|
|
59
57
|
italic: true,
|
|
60
58
|
}),
|
|
59
|
+
Text(`[...] ${tail}`, { fgColor: theme.white, wrap: "word" }),
|
|
61
60
|
);
|
|
61
|
+
} else {
|
|
62
|
+
blocks.push(Text(tail, { fgColor: theme.white, wrap: "word" }));
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
outputNode = VStack({ width: "100%" }, blocks);
|
package/src/tui-overlay.ts
CHANGED
|
@@ -183,6 +183,14 @@ export function useSelectOverlay(initialOptions: SelectOptions) {
|
|
|
183
183
|
);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
function cleanSnippet(text: string): string {
|
|
187
|
+
return text
|
|
188
|
+
.replaceAll(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "")
|
|
189
|
+
.trim()
|
|
190
|
+
.replace(/\s+/g, " ")
|
|
191
|
+
.slice(0, 80);
|
|
192
|
+
}
|
|
193
|
+
|
|
186
194
|
function sessionLabel(session: Session): string {
|
|
187
195
|
const firstUserMessage = session.messages.find(
|
|
188
196
|
(message) => message.role === "user",
|
|
@@ -195,16 +203,79 @@ function sessionLabel(session: Session): string {
|
|
|
195
203
|
: firstUserMessage.content
|
|
196
204
|
.map((block) => (block.type === "text" ? block.text : ""))
|
|
197
205
|
.join(" ");
|
|
198
|
-
const snippet = text
|
|
199
|
-
.replaceAll(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "")
|
|
200
|
-
.trim()
|
|
201
|
-
.replace(/\s+/g, " ")
|
|
202
|
-
.slice(0, 80);
|
|
206
|
+
const snippet = cleanSnippet(text);
|
|
203
207
|
|
|
204
208
|
return snippet || session.id;
|
|
205
209
|
}
|
|
206
210
|
|
|
207
|
-
|
|
211
|
+
function messageText(message: Message): string {
|
|
212
|
+
if (message.role === "user") {
|
|
213
|
+
return typeof message.content === "string"
|
|
214
|
+
? message.content
|
|
215
|
+
: message.content
|
|
216
|
+
.map((content) =>
|
|
217
|
+
content.type === "text" ? content.text : "[image]",
|
|
218
|
+
)
|
|
219
|
+
.join(" ");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (message.role === "toolResult") {
|
|
223
|
+
return message.content
|
|
224
|
+
.map((content) => (content.type === "text" ? content.text : "[image]"))
|
|
225
|
+
.join(" ");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return message.content
|
|
229
|
+
.map((content) => {
|
|
230
|
+
if (content.type === "text") return content.text;
|
|
231
|
+
if (content.type === "thinking") return content.thinking;
|
|
232
|
+
if (content.type === "toolCall") return `tool call: ${content.name}`;
|
|
233
|
+
return "";
|
|
234
|
+
})
|
|
235
|
+
.join(" ");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function messageLabel(message: Message, index: number): string {
|
|
239
|
+
const role =
|
|
240
|
+
message.role === "toolResult"
|
|
241
|
+
? `toolResult:${message.toolName}`
|
|
242
|
+
: message.role;
|
|
243
|
+
const snippet = cleanSnippet(messageText(message));
|
|
244
|
+
const prefix = `${index + 1}. ${role} ${formatTimestamp(message.timestamp)}`;
|
|
245
|
+
|
|
246
|
+
return snippet ? `${prefix} — ${snippet}` : prefix;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function forkMessages(messages: Message[], selectedIndex: number): Message[] {
|
|
250
|
+
const forkedMessages = messages.slice(0, selectedIndex + 1);
|
|
251
|
+
const toolCallIds = new Set<string>();
|
|
252
|
+
const includedToolResultIds = new Set<string>();
|
|
253
|
+
|
|
254
|
+
for (const message of forkedMessages) {
|
|
255
|
+
if (message.role === "assistant") {
|
|
256
|
+
for (const content of message.content) {
|
|
257
|
+
if (content.type === "toolCall") toolCallIds.add(content.id);
|
|
258
|
+
}
|
|
259
|
+
} else if (message.role === "toolResult") {
|
|
260
|
+
includedToolResultIds.add(message.toolCallId);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const message of messages) {
|
|
265
|
+
if (
|
|
266
|
+
message.role === "toolResult" &&
|
|
267
|
+
toolCallIds.has(message.toolCallId) &&
|
|
268
|
+
!includedToolResultIds.has(message.toolCallId)
|
|
269
|
+
) {
|
|
270
|
+
forkedMessages.push(message);
|
|
271
|
+
includedToolResultIds.add(message.toolCallId);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return forkedMessages;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function mainMenu(state: TUIState, initialPane = "main") {
|
|
208
279
|
type MenuPane = Omit<SelectOptions, "onCancel" | "onSelect">;
|
|
209
280
|
|
|
210
281
|
const oauthProviders = getOAuthProviders();
|
|
@@ -264,6 +335,15 @@ export function mainMenu(state: TUIState) {
|
|
|
264
335
|
};
|
|
265
336
|
};
|
|
266
337
|
|
|
338
|
+
const forkPane = (): MenuPane => ({
|
|
339
|
+
label: "fork",
|
|
340
|
+
filter: "",
|
|
341
|
+
list: state.messages.map((message, index) => ({
|
|
342
|
+
label: messageLabel(message, index),
|
|
343
|
+
value: String(index),
|
|
344
|
+
})),
|
|
345
|
+
});
|
|
346
|
+
|
|
267
347
|
const mainPane: MenuPane = {
|
|
268
348
|
label: "main",
|
|
269
349
|
filter: state.prompt.length ? state.prompt : "",
|
|
@@ -271,6 +351,7 @@ export function mainMenu(state: TUIState) {
|
|
|
271
351
|
{ label: "models and providers", value: "providers" },
|
|
272
352
|
{ label: "reasoning effort", value: "effort" },
|
|
273
353
|
{ label: "sessions", value: "sessions" },
|
|
354
|
+
{ label: "fork", value: "fork" },
|
|
274
355
|
],
|
|
275
356
|
};
|
|
276
357
|
const effortPane: MenuPane = {
|
|
@@ -279,7 +360,7 @@ export function mainMenu(state: TUIState) {
|
|
|
279
360
|
list: efforts,
|
|
280
361
|
};
|
|
281
362
|
const panes: MenuPane[] = [effortPane];
|
|
282
|
-
let currentPane = mainPane;
|
|
363
|
+
let currentPane = initialPane === "fork" ? forkPane() : mainPane;
|
|
283
364
|
let selectedProvider: string | undefined;
|
|
284
365
|
|
|
285
366
|
const openPane = (s: SelectState, pane: MenuPane) => {
|
|
@@ -362,7 +443,7 @@ export function mainMenu(state: TUIState) {
|
|
|
362
443
|
};
|
|
363
444
|
|
|
364
445
|
const select = useSelectOverlay({
|
|
365
|
-
...
|
|
446
|
+
...currentPane,
|
|
366
447
|
onSelect: (s) => {
|
|
367
448
|
if (!s.selected) return;
|
|
368
449
|
|
|
@@ -379,6 +460,11 @@ export function mainMenu(state: TUIState) {
|
|
|
379
460
|
});
|
|
380
461
|
}
|
|
381
462
|
|
|
463
|
+
if (s.selected === "fork") {
|
|
464
|
+
openPane(s, forkPane());
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
382
468
|
const nextPane = panes.find((pane) => pane.label === s.selected);
|
|
383
469
|
if (nextPane) openPane(s, nextPane);
|
|
384
470
|
return;
|
|
@@ -401,6 +487,28 @@ export function mainMenu(state: TUIState) {
|
|
|
401
487
|
return;
|
|
402
488
|
}
|
|
403
489
|
|
|
490
|
+
if (currentPane.label === "fork") {
|
|
491
|
+
const selectedIndex = Number(s.selected);
|
|
492
|
+
if (
|
|
493
|
+
!Number.isInteger(selectedIndex) ||
|
|
494
|
+
!state.messages[selectedIndex]
|
|
495
|
+
) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
state.sessionId = undefined;
|
|
500
|
+
state.messages = forkMessages(state.messages, selectedIndex);
|
|
501
|
+
state.tuiMessages = state.messages
|
|
502
|
+
.filter((message) => message.role !== "toolResult")
|
|
503
|
+
.map(toTUIMessage);
|
|
504
|
+
state.prompt = "";
|
|
505
|
+
state.contextSize = estimateTokens(JSON.stringify(state.messages));
|
|
506
|
+
state.scrollOffset = 0;
|
|
507
|
+
state.stickToBottom = true;
|
|
508
|
+
closeMenu(s);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
404
512
|
if (currentPane.label === "providers") {
|
|
405
513
|
const provider = currentProviders.find((v) => v === s.selected);
|
|
406
514
|
if (!provider) return;
|
package/src/tui.ts
CHANGED
|
@@ -59,6 +59,8 @@ export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
|
59
59
|
cel.render();
|
|
60
60
|
}, 1000 / fps);
|
|
61
61
|
|
|
62
|
+
let menu = mainMenu(state);
|
|
63
|
+
|
|
62
64
|
const onWindowKeyPress = (key: string) => {
|
|
63
65
|
if (key === "ctrl+q" || key === "ctrl+c" || key === "ctrl+d") {
|
|
64
66
|
// Quit
|
|
@@ -69,6 +71,10 @@ export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
|
69
71
|
// Abort or clear prompt
|
|
70
72
|
clearOrAbort(state);
|
|
71
73
|
} else if (key === "ctrl+p") {
|
|
74
|
+
menu = mainMenu(state);
|
|
75
|
+
state.overlay = true;
|
|
76
|
+
} else if (key === "ctrl+f") {
|
|
77
|
+
menu = mainMenu(state, "fork");
|
|
72
78
|
state.overlay = true;
|
|
73
79
|
}
|
|
74
80
|
};
|
|
@@ -104,8 +110,6 @@ export function initTUI(state: TUIState, leave: (s: string) => void) {
|
|
|
104
110
|
}
|
|
105
111
|
};
|
|
106
112
|
|
|
107
|
-
const menu = mainMenu(state);
|
|
108
|
-
|
|
109
113
|
cel.init(new ProcessTerminal());
|
|
110
114
|
cel.viewport(() => {
|
|
111
115
|
const layers = [
|