mini-coder 0.5.10 → 0.5.11
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/agent.ts +5 -1
- package/src/ui/commands.test.ts +2 -289
- package/src/ui/agent.test.ts +0 -49
- package/src/ui/conversation.test.ts +0 -1486
- package/src/ui/help.test.ts +0 -65
- package/src/ui/overlay.test.ts +0 -42
- package/src/ui/render-performance.test.ts +0 -444
- package/src/ui/status.test.ts +0 -489
package/package.json
CHANGED
package/src/agent.ts
CHANGED
|
@@ -623,6 +623,10 @@ function getTodoReminderSignature(
|
|
|
623
623
|
);
|
|
624
624
|
}
|
|
625
625
|
|
|
626
|
+
function wrapSystemReminder(content: string): string {
|
|
627
|
+
return `<system_reminder>\n${content}\n</system_reminder>`;
|
|
628
|
+
}
|
|
629
|
+
|
|
626
630
|
function createTodoReminderMessage(
|
|
627
631
|
messages: readonly Message[],
|
|
628
632
|
remindedTodoSignatures: Set<string>,
|
|
@@ -651,7 +655,7 @@ function createTodoReminderMessage(
|
|
|
651
655
|
|
|
652
656
|
return {
|
|
653
657
|
role: "user",
|
|
654
|
-
content: lines.join("\n"),
|
|
658
|
+
content: wrapSystemReminder(lines.join("\n")),
|
|
655
659
|
timestamp: Date.now(),
|
|
656
660
|
};
|
|
657
661
|
}
|
package/src/ui/commands.test.ts
CHANGED
|
@@ -2,40 +2,19 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import type { ContainerNode
|
|
5
|
+
import type { ContainerNode } from "@cel-tui/types";
|
|
6
6
|
import { registerFauxProvider } from "@mariozechner/pi-ai";
|
|
7
7
|
import type { AppState } from "../index.ts";
|
|
8
|
-
import { COMMANDS } from "../input.ts";
|
|
9
8
|
import {
|
|
10
|
-
appendPromptHistory,
|
|
11
9
|
computeContextTokens,
|
|
12
10
|
createSession,
|
|
13
11
|
openDatabase,
|
|
14
12
|
} from "../session.ts";
|
|
15
13
|
import { loadSettings } from "../settings.ts";
|
|
16
14
|
import { DEFAULT_THEME } from "../theme.ts";
|
|
17
|
-
import {
|
|
18
|
-
createCommandController,
|
|
19
|
-
formatPromptHistoryLabel,
|
|
20
|
-
formatPromptHistoryPreview,
|
|
21
|
-
formatRelativeDate,
|
|
22
|
-
formatSessionLabel,
|
|
23
|
-
} from "./commands.ts";
|
|
15
|
+
import { createCommandController } from "./commands.ts";
|
|
24
16
|
import type { ActiveOverlay } from "./overlay.ts";
|
|
25
17
|
|
|
26
|
-
function collectText(node: Node | null): string[] {
|
|
27
|
-
if (!node) {
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
30
|
-
if (node.type === "text") {
|
|
31
|
-
return [node.content];
|
|
32
|
-
}
|
|
33
|
-
if (node.type === "textinput") {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
return node.children.flatMap((child) => collectText(child));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
18
|
function expectOverlay(overlay: ActiveOverlay | null): ActiveOverlay {
|
|
40
19
|
if (!overlay) {
|
|
41
20
|
throw new Error("Expected an active overlay");
|
|
@@ -107,200 +86,6 @@ afterEach(() => {
|
|
|
107
86
|
});
|
|
108
87
|
|
|
109
88
|
describe("ui/commands", () => {
|
|
110
|
-
test("showCommandAutocomplete clears the current draft and opens the commands overlay", () => {
|
|
111
|
-
const state = createTestState();
|
|
112
|
-
const runtimeState = { overlay: null as ActiveOverlay | null };
|
|
113
|
-
let inputValue = "draft";
|
|
114
|
-
const controller = createCommandController({
|
|
115
|
-
openOverlay: (nextOverlay) => {
|
|
116
|
-
runtimeState.overlay = nextOverlay;
|
|
117
|
-
},
|
|
118
|
-
dismissOverlay: () => {
|
|
119
|
-
runtimeState.overlay = null;
|
|
120
|
-
},
|
|
121
|
-
setInputValue: (value) => {
|
|
122
|
-
inputValue = value;
|
|
123
|
-
},
|
|
124
|
-
appendInfoMessage: () => {},
|
|
125
|
-
appendTodoMessage: () => {},
|
|
126
|
-
scrollConversationToBottom: () => {},
|
|
127
|
-
render: () => {},
|
|
128
|
-
reloadPromptContext: async () => {},
|
|
129
|
-
openInBrowser: () => {},
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
controller.showCommandAutocomplete(state);
|
|
134
|
-
|
|
135
|
-
const overlay = expectOverlay(runtimeState.overlay);
|
|
136
|
-
|
|
137
|
-
expect(inputValue).toBe("");
|
|
138
|
-
expect(overlay.title).toBe("Commands");
|
|
139
|
-
|
|
140
|
-
const text = collectText(overlay.select());
|
|
141
|
-
for (const command of COMMANDS) {
|
|
142
|
-
expect(text.some((line) => line.includes(`/${command}`))).toBe(true);
|
|
143
|
-
}
|
|
144
|
-
} finally {
|
|
145
|
-
state.db.close();
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("showInputHistoryOverlay restores the selected raw prompt", () => {
|
|
150
|
-
const state = createTestState();
|
|
151
|
-
const runtimeState = { overlay: null as ActiveOverlay | null };
|
|
152
|
-
let inputValue = "draft";
|
|
153
|
-
const rawPrompt = "first line\nsecond line";
|
|
154
|
-
const controller = createCommandController({
|
|
155
|
-
openOverlay: (nextOverlay) => {
|
|
156
|
-
runtimeState.overlay = nextOverlay;
|
|
157
|
-
},
|
|
158
|
-
dismissOverlay: () => {
|
|
159
|
-
runtimeState.overlay = null;
|
|
160
|
-
},
|
|
161
|
-
setInputValue: (value) => {
|
|
162
|
-
inputValue = value;
|
|
163
|
-
},
|
|
164
|
-
appendInfoMessage: () => {},
|
|
165
|
-
appendTodoMessage: () => {},
|
|
166
|
-
scrollConversationToBottom: () => {},
|
|
167
|
-
render: () => {},
|
|
168
|
-
reloadPromptContext: async () => {},
|
|
169
|
-
openInBrowser: () => {},
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
appendPromptHistory(state.db, {
|
|
174
|
-
text: "older prompt",
|
|
175
|
-
cwd: "/tmp/older",
|
|
176
|
-
});
|
|
177
|
-
appendPromptHistory(state.db, { text: rawPrompt, cwd: state.cwd });
|
|
178
|
-
|
|
179
|
-
controller.showInputHistoryOverlay(state);
|
|
180
|
-
|
|
181
|
-
const overlay = expectOverlay(runtimeState.overlay);
|
|
182
|
-
|
|
183
|
-
expect(overlay.title).toBe("Input history");
|
|
184
|
-
|
|
185
|
-
const selectNode = renderSelect(overlay);
|
|
186
|
-
|
|
187
|
-
selectNode.props.onKeyPress?.("enter");
|
|
188
|
-
|
|
189
|
-
expect(runtimeState.overlay).toBeNull();
|
|
190
|
-
expect(inputValue).toBe(rawPrompt);
|
|
191
|
-
} finally {
|
|
192
|
-
state.db.close();
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test("showInputHistoryOverlay dismissal leaves the current draft unchanged", () => {
|
|
197
|
-
const state = createTestState();
|
|
198
|
-
const runtimeState = { overlay: null as ActiveOverlay | null };
|
|
199
|
-
let inputValue = "draft prompt";
|
|
200
|
-
const controller = createCommandController({
|
|
201
|
-
openOverlay: (nextOverlay) => {
|
|
202
|
-
runtimeState.overlay = nextOverlay;
|
|
203
|
-
},
|
|
204
|
-
dismissOverlay: () => {
|
|
205
|
-
runtimeState.overlay = null;
|
|
206
|
-
},
|
|
207
|
-
setInputValue: (value) => {
|
|
208
|
-
inputValue = value;
|
|
209
|
-
},
|
|
210
|
-
appendInfoMessage: () => {},
|
|
211
|
-
appendTodoMessage: () => {},
|
|
212
|
-
scrollConversationToBottom: () => {},
|
|
213
|
-
render: () => {},
|
|
214
|
-
reloadPromptContext: async () => {},
|
|
215
|
-
openInBrowser: () => {},
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
appendPromptHistory(state.db, {
|
|
220
|
-
text: "saved prompt",
|
|
221
|
-
cwd: state.cwd,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
controller.showInputHistoryOverlay(state);
|
|
225
|
-
|
|
226
|
-
const overlay = expectOverlay(runtimeState.overlay);
|
|
227
|
-
const selectNode = renderSelect(overlay);
|
|
228
|
-
|
|
229
|
-
selectNode.props.onBlur?.();
|
|
230
|
-
|
|
231
|
-
expect(runtimeState.overlay).toBeNull();
|
|
232
|
-
expect(inputValue).toBe("draft prompt");
|
|
233
|
-
} finally {
|
|
234
|
-
state.db.close();
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("handleCommand('session') overlay includes the first user preview for disambiguation", () => {
|
|
239
|
-
const state = createTestState();
|
|
240
|
-
const runtimeState = { overlay: null as ActiveOverlay | null };
|
|
241
|
-
const controller = createCommandController({
|
|
242
|
-
openOverlay: (nextOverlay) => {
|
|
243
|
-
runtimeState.overlay = nextOverlay;
|
|
244
|
-
},
|
|
245
|
-
dismissOverlay: () => {
|
|
246
|
-
runtimeState.overlay = null;
|
|
247
|
-
},
|
|
248
|
-
setInputValue: () => {},
|
|
249
|
-
appendInfoMessage: () => {},
|
|
250
|
-
appendTodoMessage: () => {},
|
|
251
|
-
scrollConversationToBottom: () => {},
|
|
252
|
-
render: () => {},
|
|
253
|
-
reloadPromptContext: async () => {},
|
|
254
|
-
openInBrowser: () => {},
|
|
255
|
-
});
|
|
256
|
-
const session = createSession(state.db, {
|
|
257
|
-
cwd: state.canonicalCwd,
|
|
258
|
-
model: "test/beta",
|
|
259
|
-
effort: "high",
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
state.db.run(
|
|
263
|
-
"INSERT INTO messages (session_id, turn, data, created_at) VALUES (?, ?, ?, ?)",
|
|
264
|
-
[
|
|
265
|
-
session.id,
|
|
266
|
-
null,
|
|
267
|
-
JSON.stringify({
|
|
268
|
-
role: "ui",
|
|
269
|
-
kind: "info",
|
|
270
|
-
content: "Help output",
|
|
271
|
-
timestamp: 1,
|
|
272
|
-
}),
|
|
273
|
-
1,
|
|
274
|
-
],
|
|
275
|
-
);
|
|
276
|
-
state.db.run(
|
|
277
|
-
"INSERT INTO messages (session_id, turn, data, created_at) VALUES (?, ?, ?, ?)",
|
|
278
|
-
[
|
|
279
|
-
session.id,
|
|
280
|
-
1,
|
|
281
|
-
JSON.stringify({
|
|
282
|
-
role: "user",
|
|
283
|
-
content: "Investigate\nthis session label",
|
|
284
|
-
timestamp: 2,
|
|
285
|
-
}),
|
|
286
|
-
2,
|
|
287
|
-
],
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
expect(controller.handleCommand("session", state)).toBe(true);
|
|
292
|
-
|
|
293
|
-
const overlay = expectOverlay(runtimeState.overlay);
|
|
294
|
-
const text = collectText(renderSelect(overlay));
|
|
295
|
-
|
|
296
|
-
expect(
|
|
297
|
-
text.some((line) => line.includes("Investigate this session")),
|
|
298
|
-
).toBe(true);
|
|
299
|
-
} finally {
|
|
300
|
-
state.db.close();
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
|
|
304
89
|
test("handleCommand('session') restores the selected session and recomputes stats", () => {
|
|
305
90
|
const state = createTestState();
|
|
306
91
|
const runtimeState = { overlay: null as ActiveOverlay | null };
|
|
@@ -391,27 +176,6 @@ describe("ui/commands", () => {
|
|
|
391
176
|
}
|
|
392
177
|
});
|
|
393
178
|
|
|
394
|
-
test("handleCommand returns false for unknown commands", () => {
|
|
395
|
-
const state = createTestState();
|
|
396
|
-
const controller = createCommandController({
|
|
397
|
-
openOverlay: () => {},
|
|
398
|
-
dismissOverlay: () => {},
|
|
399
|
-
setInputValue: () => {},
|
|
400
|
-
appendInfoMessage: () => {},
|
|
401
|
-
appendTodoMessage: () => {},
|
|
402
|
-
scrollConversationToBottom: () => {},
|
|
403
|
-
render: () => {},
|
|
404
|
-
reloadPromptContext: async () => {},
|
|
405
|
-
openInBrowser: () => {},
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
expect(controller.handleCommand("unknown", state)).toBe(false);
|
|
410
|
-
} finally {
|
|
411
|
-
state.db.close();
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
|
|
415
179
|
test("/new clears the active session state and reloads prompt context", async () => {
|
|
416
180
|
const state = createTestState();
|
|
417
181
|
let reloadCount = 0;
|
|
@@ -674,55 +438,4 @@ describe("ui/commands", () => {
|
|
|
674
438
|
state.db.close();
|
|
675
439
|
}
|
|
676
440
|
});
|
|
677
|
-
|
|
678
|
-
test("formatRelativeDate accepts an explicit clock for deterministic output", () => {
|
|
679
|
-
const now = new Date("2026-04-07T12:00:00Z");
|
|
680
|
-
|
|
681
|
-
expect(formatRelativeDate(new Date("2026-04-07T11:59:45Z"), now)).toBe(
|
|
682
|
-
"just now",
|
|
683
|
-
);
|
|
684
|
-
expect(formatRelativeDate(new Date("2026-04-07T11:50:00Z"), now)).toBe(
|
|
685
|
-
"10m ago",
|
|
686
|
-
);
|
|
687
|
-
expect(formatRelativeDate(new Date("2026-04-07T10:00:00Z"), now)).toBe(
|
|
688
|
-
"2h ago",
|
|
689
|
-
);
|
|
690
|
-
expect(formatRelativeDate(new Date("2026-04-04T12:00:00Z"), now)).toBe(
|
|
691
|
-
"3d ago",
|
|
692
|
-
);
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
test("formatPromptHistoryPreview collapses whitespace into one line", () => {
|
|
696
|
-
expect(formatPromptHistoryPreview(" first\n\n second\tthird ")).toBe(
|
|
697
|
-
"first second third",
|
|
698
|
-
);
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
test("formatPromptHistoryLabel_longPromptAndCwd_returnsATruncatedSingleLineLabel", () => {
|
|
702
|
-
expect(
|
|
703
|
-
formatPromptHistoryLabel(
|
|
704
|
-
" Investigate\n\n this prompt history row because it is much too wide for the overlay ",
|
|
705
|
-
"/tmp/projects/very/deeply/nested/mini-coder-audit",
|
|
706
|
-
"5m ago",
|
|
707
|
-
),
|
|
708
|
-
).toBe(
|
|
709
|
-
"Investigate this prompt history… · …/mini-coder-audit · 5m ago",
|
|
710
|
-
);
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
test("formatSessionLabel_longPreviewAndModel_returnsATruncatedReadableLabel", () => {
|
|
714
|
-
expect(
|
|
715
|
-
formatSessionLabel(
|
|
716
|
-
{
|
|
717
|
-
model: "openai-codex/gpt-5.4-super-long-variant",
|
|
718
|
-
firstUserPreview:
|
|
719
|
-
"Audit the session selector because every entry looks identical in real usage",
|
|
720
|
-
},
|
|
721
|
-
"just now",
|
|
722
|
-
true,
|
|
723
|
-
),
|
|
724
|
-
).toBe(
|
|
725
|
-
"Audit the session selector… · openai-codex/gpt… · just now · current",
|
|
726
|
-
);
|
|
727
|
-
});
|
|
728
441
|
});
|
package/src/ui/agent.test.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { isEmptyUserContent, stripSkillFrontmatter } from "./agent.ts";
|
|
3
|
-
|
|
4
|
-
describe("ui/agent", () => {
|
|
5
|
-
test("stripSkillFrontmatter removes frontmatter and keeps the skill body", () => {
|
|
6
|
-
const content = [
|
|
7
|
-
"---",
|
|
8
|
-
"name: code-review",
|
|
9
|
-
'description: "Review code for issues"',
|
|
10
|
-
"---",
|
|
11
|
-
"# Review Checklist",
|
|
12
|
-
"- Find bugs",
|
|
13
|
-
"",
|
|
14
|
-
].join("\n");
|
|
15
|
-
|
|
16
|
-
expect(stripSkillFrontmatter(content)).toBe(
|
|
17
|
-
"# Review Checklist\n- Find bugs\n",
|
|
18
|
-
);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("stripSkillFrontmatter leaves content without frontmatter unchanged", () => {
|
|
22
|
-
expect(stripSkillFrontmatter("# Skill\nUse this carefully\n")).toBe(
|
|
23
|
-
"# Skill\nUse this carefully\n",
|
|
24
|
-
);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("isEmptyUserContent returns true for empty text-only content", () => {
|
|
28
|
-
expect(isEmptyUserContent(" \n\t")).toBe(true);
|
|
29
|
-
expect(
|
|
30
|
-
isEmptyUserContent([
|
|
31
|
-
{ type: "text", text: " " },
|
|
32
|
-
{ type: "text", text: "\n" },
|
|
33
|
-
]),
|
|
34
|
-
).toBe(true);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("isEmptyUserContent returns false when multipart content includes an image", () => {
|
|
38
|
-
expect(
|
|
39
|
-
isEmptyUserContent([
|
|
40
|
-
{ type: "text", text: " " },
|
|
41
|
-
{
|
|
42
|
-
type: "image",
|
|
43
|
-
data: Buffer.from("fake-png-data").toString("base64"),
|
|
44
|
-
mimeType: "image/png",
|
|
45
|
-
},
|
|
46
|
-
]),
|
|
47
|
-
).toBe(false);
|
|
48
|
-
});
|
|
49
|
-
});
|