mini-coder 0.6.1 → 0.6.3

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/src/prompt.ts CHANGED
@@ -2,61 +2,21 @@ import { promises } from "node:fs";
2
2
  import { readdir } from "node:fs/promises";
3
3
  import { homedir, platform } from "node:os";
4
4
  import { join } from "node:path";
5
- import type { Message, ToolCall, ToolResultMessage } from "@mariozechner/pi-ai";
6
- import simpleGit, { type StatusResult } from "simple-git";
5
+ import type { Message, ToolResultMessage } from "@earendil-works/pi-ai";
6
+ import { getGitStatus } from "./git";
7
7
  import { parseSkillFrontmatter } from "./shared";
8
8
 
9
- export const MAIN_PROMPT = `# You are "mini-coder", a coding agent.
9
+ export const MAIN_PROMPT = `You are a coding agent interacting with users via the mini-coder harness. You help users by reading files, executing commands, and editting code.
10
10
 
11
- IMPORTANT: Be defensive with existing changes and destructive commands.
12
- IMPORTANT: Do not overstate what changed or what was verified. Summaries must match the diff.
13
-
14
- ## Role
15
- You help users by reading files, executing commands, editing code, and writing new files. Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without unnecessary superlatives, praise, or emotional validation.
16
-
17
- <example>
18
- When referencing specific functions or pieces of code, include the pattern \`file_path:line_number\`.
19
- For example: "Clients are handled in the \`connectToServer\` function in src/services/process.ts:712."
20
- </example>
21
-
22
- User messages and Tool results may include <system-reminder> tags. These contain system-generated reminders and bear no direct relation to the specific tool result in which they appear.
23
-
24
- ## Tools
11
+ - Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without unnecessary superlatives, praise, or emotional validation.
12
+ - User messages and Tool results may include <system-reminder> tags. These contain system-generated reminders and bear no direct relation to the specific tool result in which they appear.
25
13
  - You have access to bash, read and edit tools. Prefer using read and edit for file operations, use bash for finding read candidates or to run development commands.
26
-
27
- <example>
28
- > User: please read the README.md and add rich code examples.
29
-
30
- - Use the bash tool to find the path for README.md, prefer "ls" or "fd/find", and "rg/grep".
31
- - Then read the file with the read tool to find the replacement areas and mathcing patterns
32
- - Edit the file using the edit tool. Review the output diff, use the read tool again to verify if needed.
33
- - Reply to the user that the edit was done.
34
- </example>
35
-
36
- ## Workflow
37
- - Stay rooted on the user's request. Don't wander into tangents or explore out of curiosity.
38
- - Gather only the information needed to fulfill the request, then stop exploring and complete it.
39
- - Narrate your edits with brief commentary during long tasks so the user can follow progress.
40
- - Verify your changes via compilation, tests, or manual checks whenever possible.
41
-
42
- ## Tone
43
- - Be concise. Use a professional colleague tone: direct, never condescending, and never rude.
44
-
45
- ## Error Handling
46
14
  - If a tool call fails or is denied, do NOT re-attempt the exact same call. Analyze why it failed and adjust your approach.
47
-
48
- ## Safety rules
49
- - Answer all user requests without guessing, or assuming. Verify your answers and claims before making them.
50
15
  - Use recent online information, the current environment, and your training data combined for a complete answer.
51
16
  - Ensure that you fulfill the user's expectation, requirements and contract **exactly**.
52
- - Be defensive with existing changes and destructive commands, they could harm your user's changes.
53
17
  - Use temp directory for temp files, scripts, plan files, or anything that doesn't match the requested output.
54
- - Do not over-scope your work, or add more scope during implementation.
55
- - Avoid over-enginnering, hacks or creative solutions. The boring, simple and repliable is always preferred.
18
+ - Be concise. Use a professional colleague tone: direct, never condescending, and never rude.
56
19
  - Do not overstate what changed or what was verified. Summaries must match the diff.
57
-
58
- IMPORTANT: Never guess or assume. Verify claims before making them.
59
- IMPORTANT: Do not over-scope work or add scope during implementation.
60
20
  `;
61
21
 
62
22
  async function getDir() {
@@ -76,12 +36,7 @@ async function getDir() {
76
36
 
77
37
  async function getEnvPrompt() {
78
38
  // TODO: What else do the agents always check before answering every time?
79
- let gitStatus: StatusResult | { nogit: string };
80
- try {
81
- gitStatus = await simpleGit().status();
82
- } catch (_) {
83
- gitStatus = { nogit: "No git repo in this folder." };
84
- }
39
+ const gitStatus = await getGitStatus();
85
40
  const envKeys = ["PATH", "USER", "LANG", "HOME", "SHELL", "BUN_INSTALL"];
86
41
  const env: Record<string, string> = {};
87
42
  for (const key of envKeys) {
@@ -211,47 +166,68 @@ export async function injectEnvReminder(): Promise<string> {
211
166
  return `<system-reminder>\n${envStatus}\n</system-reminder>`;
212
167
  }
213
168
 
214
- // TODO: Needs to be updated since we are deprecating the task tool
215
- // for now. Needs to check for similar or identical tool calls, aka
216
- // Doom looping.
169
+ const doomLoopReminder = `<system-reminder>
170
+ You may be entering a tool-call doom loop: recent tool usage is repetitive or not clearly progressing.
171
+
172
+ - Stop repeating the same or similar tool call unless new evidence requires it.
173
+ - Re-read the user's request and summarize what is known, what failed, and what is still needed.
174
+ - Change strategy before calling more tools: narrow the next check, use a different source of evidence, or ask the user if blocked.
175
+ - If the request is complete, stop calling tools and provide the final answer.
176
+ </system-reminder>`;
177
+
178
+ // Checks recent tool usage for simple repeated-call patterns and inserts an
179
+ // anti-doom-loop reminder when the agent appears stuck.
217
180
  export function insertToolUsageReminder(
218
181
  messages: Message[],
219
182
  toolMessage: ToolResultMessage,
220
- ) {
221
- // check for the last 5 tool call assistant messages
222
- // if they are non-`task` tool calls insert the reminder
223
- // as a prefix.
224
- let output = toolMessage.content
225
- .filter((b) => b.type === "text")
226
- .map((b) => b.text)
227
- .join("\n");
228
-
229
- const budget = 5;
230
- const toolCalls: ToolCall[] = [];
231
- const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
232
- const messagesSinceLastUser = messages.slice(lastUserMessageIndex + 1);
233
-
234
- messagesSinceLastUser.forEach((m) => {
235
- if (m.role === "assistant") {
236
- const toolCallsBlocks = m.content.filter((b) => b.type === "toolCall");
237
- toolCalls.push(...toolCallsBlocks);
238
- }
183
+ ): ToolResultMessage {
184
+ const lastReminderIdx = messages.findLastIndex((m) => {
185
+ return (
186
+ m.role === "toolResult" &&
187
+ m.content.find(
188
+ (b) => b.type === "text" && b.text.includes(doomLoopReminder),
189
+ )
190
+ );
191
+ });
192
+ const lastUserMessageIdx = messages.findLastIndex((m) => {
193
+ return m.role === "user";
239
194
  });
240
- const recentToolCalls = toolCalls.slice(-budget);
241
- const taskSeen = recentToolCalls.some((call) => call.name === "task");
195
+ const messagesSinceLast = messages.slice(
196
+ Math.max(lastReminderIdx, lastUserMessageIdx) + 1,
197
+ );
198
+ const minMessagesForReminder = 4;
199
+
200
+ if (messagesSinceLast.length < minMessagesForReminder) return toolMessage;
242
201
 
243
- if (toolCalls.length >= budget && !taskSeen) {
244
- output = `<system-reminder>
245
- You are currently making repeated individual tool calls. This fragments context and reduces efficiency.
202
+ let errorCount = 0;
203
+ let sameToolCount = 0;
204
+ const seenArgs = new Set<string>();
246
205
 
247
- - Stop and plan: consolidate remaining steps into a single **task** tool call.
248
- - If the user request is fully completed, stop calling tools and provide your final answer.
249
- </system-reminder>
206
+ for (const msg of messagesSinceLast) {
207
+ if (msg.role === "toolResult" && msg.isError) errorCount++;
250
208
 
251
- ${output}`;
209
+ if (msg.role === "assistant") {
210
+ const calls = msg.content.filter((b) => b.type === "toolCall");
211
+ for (const c of calls) {
212
+ const args = JSON.stringify(c.arguments);
213
+ if (seenArgs.has(args)) sameToolCount++;
214
+ seenArgs.add(args);
215
+ }
216
+ }
252
217
  }
253
218
 
254
- toolMessage.content = [{ type: "text", text: output }];
219
+ if (
220
+ errorCount >= minMessagesForReminder ||
221
+ sameToolCount >= minMessagesForReminder
222
+ ) {
223
+ return {
224
+ ...toolMessage,
225
+ content: [
226
+ ...toolMessage.content,
227
+ { type: "text", text: doomLoopReminder },
228
+ ],
229
+ };
230
+ }
255
231
 
256
232
  return toolMessage;
257
233
  }
package/src/session.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  import { mkdir } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import type { Message } from "@mariozechner/pi-ai";
3
+ import type { Message } from "@earendil-works/pi-ai";
4
4
  import { Value } from "typebox/value";
5
5
  import { SESSIONS_DIR } from "./shared";
6
6
  import { type Session, SessionSchema } from "./types";
7
7
 
8
- // TODO: sessions are json files in SESSIONS_DIR inside of DATA_DIR, use a 10 length `secureRandomString()` for the ids.
9
-
10
8
  export async function ensureSessionsDir(): Promise<void> {
11
9
  await mkdir(SESSIONS_DIR, { recursive: true });
12
10
  }
@@ -71,8 +69,12 @@ export async function saveSession(s: Session) {
71
69
  export async function updateSession(id: string, messages: Message[]) {
72
70
  const existing = await getSession(id);
73
71
  if (existing) {
74
- existing.messages = messages;
75
- await saveSession(existing);
72
+ // Only append new messages, so we don't save compacted messages.
73
+ if (existing.messages.length < messages.length) {
74
+ const newMessages = messages.slice(existing.messages.length);
75
+ existing.messages = [...existing.messages, ...newMessages];
76
+ await saveSession(existing);
77
+ }
76
78
  return;
77
79
  }
78
80
 
package/src/tool-bash.ts CHANGED
@@ -1,24 +1,10 @@
1
- import { type Tool, Type } from "@mariozechner/pi-ai";
2
- import { secureRandomString } from "./shared";
1
+ import { type Tool, Type } from "@earendil-works/pi-ai";
3
2
  import type { ToolRunnerEvent } from "./types";
4
3
 
5
- const OUTPUT_THRESHOLD = 16000;
6
- const description = `## Bash CLI tool
4
+ const description = `Bash CLI tool
7
5
 
8
6
  Execute shell commands on the user's environment.
9
7
 
10
- Best practices:
11
-
12
- - Use \`ls\` to list files.
13
- - Use \`fd\` or \`find\` to locate files/directories by name, type, size, time, permissions, etc.
14
- - Use \`rg\` or \`grep\` to search inside files for matching patterns.
15
- - Use \`cat -n\` or \`nl\` to read small files, or when you need the whole file.
16
- - Use \`sed -n\` with ranges to read sections of files. Prefer targeted reads. Avoid dumping very large (more than ~200 lines) files all at once.
17
- - Use \`cp\`, \`mv\`, and \`mkdir\` for file and directory operations, and \`rm\` to remove files and directories.
18
- - Use \`curl\` for web access. Use redirection to temp files for targeted reads.
19
- - Use development tools like \`git\`, \`gh\`, \`jq\`, etc, when appropriate.
20
- - Prefer \`cp -i\`, \`mv -i\`, \`rm -i\` when learning.
21
- - NEVER run destructive commands (\`rm -rf\`, \`git reset --hard\`, overwriting files) without confirming the target first.
22
8
  - Chain commands **only** when failure should stop the flow. Avoid long chains, **2 to 3 maximum**.
23
9
  - Avoid overly complex one-liners; readability matters.
24
10
  - Quote filenames: use \`"$file"\` not \`$file\`.
@@ -80,25 +66,9 @@ export async function* runBashTool(
80
66
 
81
67
  const exitCode = await proc.exited;
82
68
 
83
- let result = `# EXIT CODE: ${exitCode}`;
69
+ let result = `${output.length ? output : "(no ouput)"}\n\nExit code: ${exitCode}`;
84
70
  if (output.length) {
85
- result += `
86
- # OUTPUT:
87
-
88
- ${output}`;
89
- }
90
-
91
- // If `out` is too big, more than ~XXKB, write it to a temp file
92
- // And add that to the truncation label for the agent to be able
93
- // to continue the read with scans. This is to protect context,
94
- // not a general read guard. The hint is for the agent, not the TUI
95
- if (result.length > OUTPUT_THRESHOLD) {
96
- const key = `${Date.now()}-${secureRandomString(4)}`;
97
- const pathname = `/tmp/bash_result_${key}.txt`;
98
- await Bun.write(pathname, result);
99
- result = `${result.substring(0, OUTPUT_THRESHOLD)}
100
-
101
- Truncated at ~${OUTPUT_THRESHOLD / 1000}KB. Full output at ${pathname}`;
71
+ result += `${output}`;
102
72
  }
103
73
 
104
74
  yield {
package/src/tool-edit.ts CHANGED
@@ -1,35 +1,23 @@
1
1
  import { isAbsolute, join } from "node:path";
2
- import { type Tool, Type } from "@mariozechner/pi-ai";
2
+ import { type Tool, Type } from "@earendil-works/pi-ai";
3
3
  import { createPatch } from "diff";
4
4
  import type { ToolRunnerEvent } from "./types";
5
5
 
6
- const description = `## Edit tool
6
+ const description = `Edit tool
7
7
 
8
8
  A find-and-replace file editor. Use it to create new files or modify existing ones safely. Always prefer this tool over bash editing methods (sed, awk, etc).
9
9
 
10
- ### Rules
10
+ Rules
11
+
11
12
  - The tool refuses to edit on multiple matches of \`oldText\`. Be specific with your matching text.
12
13
  - Prefer patch-based edits (small targeted replacements) for multi-line or semantic changes.
13
14
  - Do NOT reproduce entire files. Use shell file operations (\`cp\`, \`mv\`, etc) for wholesale file replacement instead.
14
15
 
15
- ### Failure modes
16
+ Failure modes
17
+
16
18
  - If \`oldText\` is not found, the edit fails. Verify the exact text first.
17
19
  - If \`oldText\` matches multiple locations, the edit fails. Narrow your match and retry.
18
20
  - If the file does not exist and \`oldText\` is non-empty, the edit fails.
19
-
20
- <example>
21
- Edit a single line:
22
- path: src/utils.ts
23
- oldText: const MAX_RETRIES = 3;
24
- newText: const MAX_RETRIES = 5;
25
- </example>
26
-
27
- <example>
28
- Patch-based edit (preferred for multi-line changes):
29
- path: src/utils.ts
30
- oldText: function oldHelper() {\n return 1;\n}
31
- newText: function newHelper() {\n return 2;\n}\n\nfunction oldHelper() {\n return 1;\n}
32
- </example>
33
21
  `;
34
22
 
35
23
  export const edit: Tool = {
package/src/tool-read.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from "node:buffer";
2
2
  import { extname, isAbsolute, join } from "node:path";
3
- import { type Tool, Type } from "@mariozechner/pi-ai";
3
+ import { type Tool, Type } from "@earendil-works/pi-ai";
4
4
  import type { ToolRunnerEvent } from "./types";
5
5
 
6
6
  const imageMimeTypes: Record<string, string> = {
@@ -13,7 +13,7 @@ const imageMimeTypes: Record<string, string> = {
13
13
  ".webp": "image/webp",
14
14
  };
15
15
 
16
- const description = `## Read tool
16
+ const description = `Read tool
17
17
 
18
18
  Read a file by path.
19
19
 
@@ -114,11 +114,11 @@ export function ContextPill(state: TUIState) {
114
114
  const smartPercent = Math.floor((state.contextSize / smartMax) * 100);
115
115
  if (smartPercent > 90) {
116
116
  bg = theme.bred;
117
- } else if (smartPercent > 80) {
117
+ } else if (smartPercent > 85) {
118
118
  bg = theme.red;
119
- } else if (smartPercent > 70) {
119
+ } else if (smartPercent > 80) {
120
120
  bg = theme.yellow;
121
- } else if (smartPercent > 50) {
121
+ } else if (smartPercent > 60) {
122
122
  bg = theme.byellow;
123
123
  }
124
124
  text = `~${percent}%`;
@@ -126,7 +126,7 @@ export function ContextPill(state: TUIState) {
126
126
 
127
127
  text += ` (${state.options.model.contextWindow / 1000}k)`;
128
128
 
129
- return TextPill(text, theme.bblack, bg);
129
+ return TextPill(text, theme.black, bg);
130
130
  }
131
131
 
132
132
  export function GitPill(state: TUIState) {