mini-coder 0.6.0 → 0.6.2

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,74 +2,23 @@ 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";
5
+ import type { Message, ToolResultMessage } from "@earendil-works/pi-ai";
6
6
  import simpleGit, { type StatusResult } from "simple-git";
7
7
  import { parseSkillFrontmatter } from "./shared";
8
8
 
9
- const safetyPrompt = `
10
- # Safety rules
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.
11
10
 
12
- - Answer all user requests without guessing, or assuming. Verify your answers and claims before making them.
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.
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.
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.
13
15
  - Use recent online information, the current environment, and your training data combined for a complete answer.
14
16
  - Ensure that you fulfill the user's expectation, requirements and contract **exactly**.
15
- - Be defensive with existing changes and destructive commands, they could harm your user's changes.
16
17
  - Use temp directory for temp files, scripts, plan files, or anything that doesn't match the requested output.
17
- - Do not over-scope your work, or add more scope during implementation.
18
- - 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.
19
19
  - Do not overstate what changed or what was verified. Summaries must match the diff.
20
20
  `;
21
21
 
22
- export const MAIN_PROMPT = `# You are "mini-coder", a coding agent.
23
-
24
- IMPORTANT: Be defensive with existing changes and destructive commands.
25
- IMPORTANT: Do not overstate what changed or what was verified. Summaries must match the diff.
26
-
27
- ## Role
28
- 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.
29
-
30
- 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.
31
-
32
- ## Tool Usage
33
- - The **task** tool is the preferred way to work. Use it for multi-step research, exploration, or implementation tasks.
34
- - Use **bash**, **edit**, and other tools only for single, immediate actions.
35
- - **Do NOT** chain multiple bash/edit calls for multi-step work. Use **task** instead.
36
- - If a job needs more than one tool call, use the **task** tool instead of chaining individual calls.
37
-
38
- <example>
39
- When referencing specific functions or pieces of code, include the pattern \`file_path:line_number\`.
40
- For example: "Clients are handled in the \`connectToServer\` function in src/services/process.ts:712."
41
- </example>
42
-
43
- ## Workflow
44
- - Stay rooted on the user's request. Don't wander into tangents or explore out of curiosity.
45
- - Gather only the information needed to fulfill the request, then stop exploring and complete it.
46
- - Narrate your edits with brief commentary during long tasks so the user can follow progress.
47
- - Verify your changes via compilation, tests, or manual checks whenever possible.
48
-
49
- ## Tone
50
- - Be concise. Use a jovial but motivated colleague tone: direct, never condescending, and never rude.
51
-
52
- ## Error Handling
53
- - If a tool call fails or is denied, do NOT re-attempt the exact same call. Analyze why it failed and adjust your approach.
54
-
55
- IMPORTANT: Never guess or assume. Verify claims before making them.
56
- IMPORTANT: Do not over-scope work or add scope during implementation.
57
-
58
- ${safetyPrompt}
59
- `;
60
-
61
- export const TASK_PROMPT = `# You are an efficient, elite-level task Agent
62
-
63
- ## Role
64
- Execute the assigned task precisely and efficiently. Prioritize correctness over speed. Focus on facts and objective technical details.
65
-
66
- ## Output Requirements
67
- - Your final response must include all actions taken and exact diffs of any changes made.
68
- - Provide a concise report of what was done, what was verified, and any decisions made.
69
-
70
- ${safetyPrompt}
71
- `;
72
-
73
22
  async function getDir() {
74
23
  const ignoreFile = Bun.file(".gitignore");
75
24
  let ignoreContent = "";
@@ -222,44 +171,68 @@ export async function injectEnvReminder(): Promise<string> {
222
171
  return `<system-reminder>\n${envStatus}\n</system-reminder>`;
223
172
  }
224
173
 
174
+ const doomLoopReminder = `<system-reminder>
175
+ You may be entering a tool-call doom loop: recent tool usage is repetitive or not clearly progressing.
176
+
177
+ - Stop repeating the same or similar tool call unless new evidence requires it.
178
+ - Re-read the user's request and summarize what is known, what failed, and what is still needed.
179
+ - Change strategy before calling more tools: narrow the next check, use a different source of evidence, or ask the user if blocked.
180
+ - If the request is complete, stop calling tools and provide the final answer.
181
+ </system-reminder>`;
182
+
183
+ // Checks recent tool usage for simple repeated-call patterns and inserts an
184
+ // anti-doom-loop reminder when the agent appears stuck.
225
185
  export function insertToolUsageReminder(
226
186
  messages: Message[],
227
187
  toolMessage: ToolResultMessage,
228
- ) {
229
- // check for the last 5 tool call assistant messages
230
- // if they are non-`task` tool calls insert the reminder
231
- // as a prefix.
232
- let output = toolMessage.content
233
- .filter((b) => b.type === "text")
234
- .map((b) => b.text)
235
- .join("\n");
236
-
237
- const budget = 3;
238
- const toolCalls: ToolCall[] = [];
239
- const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
240
- const messagesSinceLastUser = messages.slice(lastUserMessageIndex + 1);
241
-
242
- messagesSinceLastUser.forEach((m) => {
243
- if (m.role === "assistant") {
244
- const toolCallsBlocks = m.content.filter((b) => b.type === "toolCall");
245
- toolCalls.push(...toolCallsBlocks);
246
- }
188
+ ): ToolResultMessage {
189
+ const lastReminderIdx = messages.findLastIndex((m) => {
190
+ return (
191
+ m.role === "toolResult" &&
192
+ m.content.find(
193
+ (b) => b.type === "text" && b.text.includes(doomLoopReminder),
194
+ )
195
+ );
196
+ });
197
+ const lastUserMessageIdx = messages.findLastIndex((m) => {
198
+ return m.role === "user";
247
199
  });
248
- const recentToolCalls = toolCalls.slice(-budget);
249
- const taskSeen = recentToolCalls.some((call) => call.name === "task");
200
+ const messagesSinceLast = messages.slice(
201
+ Math.max(lastReminderIdx, lastUserMessageIdx) + 1,
202
+ );
203
+ const minMessagesForReminder = 4;
204
+
205
+ if (messagesSinceLast.length < minMessagesForReminder) return toolMessage;
250
206
 
251
- if (toolCalls.length >= budget && !taskSeen) {
252
- output = `<system-reminder>
253
- You are currently making repeated individual tool calls. This fragments context and reduces efficiency.
207
+ let errorCount = 0;
208
+ let sameToolCount = 0;
209
+ const seenArgs = new Set<string>();
254
210
 
255
- - Stop and plan: consolidate remaining steps into a single **task** tool call.
256
- - If the user request is fully completed, stop calling tools and provide your final answer.
257
- </system-reminder>
211
+ for (const msg of messagesSinceLast) {
212
+ if (msg.role === "toolResult" && msg.isError) errorCount++;
258
213
 
259
- ${output}`;
214
+ if (msg.role === "assistant") {
215
+ const calls = msg.content.filter((b) => b.type === "toolCall");
216
+ for (const c of calls) {
217
+ const args = JSON.stringify(c.arguments);
218
+ if (seenArgs.has(args)) sameToolCount++;
219
+ seenArgs.add(args)
220
+ }
221
+ }
260
222
  }
261
223
 
262
- toolMessage.content = [{ type: "text", text: output }];
224
+ if (
225
+ errorCount >= minMessagesForReminder ||
226
+ sameToolCount >= minMessagesForReminder
227
+ ) {
228
+ return {
229
+ ...toolMessage,
230
+ content: [
231
+ ...toolMessage.content,
232
+ { type: "text", text: doomLoopReminder },
233
+ ],
234
+ };
235
+ }
263
236
 
264
237
  return toolMessage;
265
238
  }
package/src/session.ts CHANGED
@@ -1,6 +1,6 @@
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";
package/src/tool-bash.ts CHANGED
@@ -1,24 +1,12 @@
1
- import { type Tool, Type } from "@mariozechner/pi-ai";
1
+ import { type Tool, Type } from "@earendil-works/pi-ai";
2
2
  import { secureRandomString } from "./shared";
3
3
  import type { ToolRunnerEvent } from "./types";
4
4
 
5
5
  const OUTPUT_THRESHOLD = 16000;
6
- const description = `## Bash CLI tool
6
+ const description = `Bash CLI tool
7
7
 
8
8
  Execute shell commands on the user's environment.
9
9
 
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
10
  - Chain commands **only** when failure should stop the flow. Avoid long chains, **2 to 3 maximum**.
23
11
  - Avoid overly complex one-liners; readability matters.
24
12
  - Quote filenames: use \`"$file"\` not \`$file\`.
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 = {
@@ -126,7 +114,7 @@ export async function* runEditTool(
126
114
 
127
115
  yield {
128
116
  type: "result",
129
- text: `File edited: ${filePath}\n\n${patch}`,
117
+ text: patch,
130
118
  };
131
119
 
132
120
  return;
@@ -0,0 +1,100 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { extname, isAbsolute, join } from "node:path";
3
+ import { type Tool, Type } from "@earendil-works/pi-ai";
4
+ import type { ToolRunnerEvent } from "./types";
5
+
6
+ const imageMimeTypes: Record<string, string> = {
7
+ ".bmp": "image/bmp",
8
+ ".gif": "image/gif",
9
+ ".jpeg": "image/jpeg",
10
+ ".jpg": "image/jpeg",
11
+ ".png": "image/png",
12
+ ".svg": "image/svg+xml",
13
+ ".webp": "image/webp",
14
+ };
15
+
16
+ const description = `Read tool
17
+
18
+ Read a file by path.
19
+
20
+ For text files, returns line-numbered text. Use \`offset\` and \`limit\` to read a specific range.
21
+ You can also read images files.
22
+ `;
23
+
24
+ export const read: Tool = {
25
+ name: "read",
26
+ description,
27
+ parameters: Type.Object({
28
+ path: Type.String({
29
+ description:
30
+ "File path. Absolute or relative to the current working directory.",
31
+ }),
32
+ offset: Type.Optional(
33
+ Type.Number({
34
+ description:
35
+ "Optional 1-based line number to start reading from. Text files only.",
36
+ }),
37
+ ),
38
+ limit: Type.Optional(
39
+ Type.Number({
40
+ description:
41
+ "Optional maximum number of lines to read. Text files only.",
42
+ }),
43
+ ),
44
+ }),
45
+ };
46
+
47
+ export async function* runReadTool(
48
+ args: Record<string, any>,
49
+ signal?: AbortSignal,
50
+ ): AsyncGenerator<ToolRunnerEvent> {
51
+ const filePath = isAbsolute(args.path)
52
+ ? args.path
53
+ : join(process.cwd(), args.path);
54
+ const file = Bun.file(filePath);
55
+ const exists = await file.exists();
56
+
57
+ if (!exists) {
58
+ yield { type: "result", text: `File not found: ${filePath}` };
59
+ return;
60
+ }
61
+
62
+ if (signal?.aborted) {
63
+ yield { type: "result", text: "Aborted before read." };
64
+ return;
65
+ }
66
+
67
+ const mimeType = imageMimeTypes[extname(filePath).toLowerCase()] ?? file.type;
68
+ if (mimeType.startsWith("image/")) {
69
+ const data = Buffer.from(await file.arrayBuffer()).toString("base64");
70
+ const text = `Image read: ${filePath}\nMIME type: ${mimeType}\nSize: ${file.size} bytes`;
71
+ yield {
72
+ type: "result",
73
+ text,
74
+ image: { data, mimeType },
75
+ };
76
+ return;
77
+ }
78
+
79
+ const content = await file.text();
80
+ const lines = content.split(/\r?\n/);
81
+ if (content.endsWith("\n")) lines.pop();
82
+
83
+ const offset = args.offset ?? 1;
84
+ const startIndex = offset - 1;
85
+ const endIndex = args.limit
86
+ ? Math.min(lines.length, startIndex + args.limit)
87
+ : lines.length;
88
+ const width = String(endIndex).length;
89
+ const body = lines
90
+ .slice(startIndex, endIndex)
91
+ .map((line, idx) => `${String(offset + idx).padStart(width)} | ${line}`)
92
+ .join("\n");
93
+
94
+ let text = `File: ${filePath}\nLines: ${offset}-${endIndex} of ${lines.length}\n\n${body}`;
95
+ if (endIndex < lines.length) {
96
+ text += `\n\nMore lines available. Use offset ${endIndex + 1} to continue.`;
97
+ }
98
+
99
+ yield { type: "result", text };
100
+ }
@@ -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) {
@@ -137,13 +137,13 @@ export function ModelPill(state: TUIState) {
137
137
  // Like loot: Gold/purple/blue/green/white -> xhigh/high/medium/low/minimal
138
138
  switch (state.options.effort) {
139
139
  case "xhigh":
140
- return TextPill(state.options.model.name, theme.white, theme.yellow);
140
+ return TextPill(state.options.model.name, theme.black, theme.yellow);
141
141
  case "high":
142
- return TextPill(state.options.model.name, theme.white, theme.magenta);
142
+ return TextPill(state.options.model.name, theme.black, theme.magenta);
143
143
  case "medium":
144
- return TextPill(state.options.model.name, theme.white, theme.blue);
144
+ return TextPill(state.options.model.name, theme.black, theme.blue);
145
145
  case "low":
146
- return TextPill(state.options.model.name, theme.white, theme.green);
146
+ return TextPill(state.options.model.name, theme.black, theme.green);
147
147
  }
148
148
  // Minimal
149
149
  return TextPill(state.options.model.name, theme.bwhite, theme.bblack);
@@ -5,7 +5,7 @@ import type {
5
5
  Message,
6
6
  ToolResultMessage,
7
7
  UserMessage,
8
- } from "@mariozechner/pi-ai";
8
+ } from "@earendil-works/pi-ai";
9
9
  import { estimateTokens, relativeTime } from "./shared";
10
10
  import { TextPill, theme } from "./tui-components";
11
11
  import type { TUIState } from "./types";
@@ -33,9 +33,6 @@ function agentMessageNode(msg: AssistantMessage): Node {
33
33
  } else if ("command" in block.arguments) {
34
34
  text = block.arguments.command;
35
35
  node = SyntaxHighlight(text, "bash");
36
- } else if ("prompt" in block.arguments) {
37
- text = block.arguments.prompt;
38
- node = SyntaxHighlight(text, "markdown");
39
36
  } else {
40
37
  text = JSON.stringify(block.arguments);
41
38
  node = Text(text);
@@ -119,22 +116,33 @@ function userMessageNode(msg: UserMessage): Node {
119
116
  }
120
117
 
121
118
  function toolMessageNode(msg: ToolResultMessage): Node {
119
+ // TODO: read tool image output
122
120
  // Output only shows last 10 lines of scroll.
123
- return VStack(
124
- {
125
- height: 10,
126
- padding: { x: 4 },
127
- overflow: "scroll",
128
- scrollOffset: Infinity,
129
- onScroll: () => false,
130
- },
131
- msg.content.map((block) => {
132
- if (block.type === "text") {
133
- return Text(block.text, { wrap: "word", fgColor: theme.bblack });
134
- }
135
- return Text(""); // TODO: image case needs attention
121
+ const text = msg.content
122
+ .filter((c) => c.type === "text")
123
+ .map((c) => c.text)
124
+ .join("")
125
+ .trim();
126
+
127
+ return VStack({ padding: { x: 4 }, gap: 1 }, [
128
+ Text(`~${estimateTokens(text)} tokens, ${text.split("\n").length} lines.`, {
129
+ fgColor: theme.bblack,
136
130
  }),
137
- );
131
+ VStack(
132
+ {
133
+ flex: 1,
134
+ maxHeight: msg.toolName === "edit" ? 20 : 10,
135
+ overflow: "scroll",
136
+ scrollOffset: Infinity,
137
+ onScroll: () => false,
138
+ },
139
+ [
140
+ msg.toolName === "edit"
141
+ ? SyntaxHighlight(text, "diff")
142
+ : Text(text, { wrap: "word", fgColor: theme.white }),
143
+ ],
144
+ ),
145
+ ]);
138
146
  }
139
147
 
140
148
  const messageBodyCache = new WeakMap<Message, { key: number; node: Node }>();
@@ -190,6 +198,7 @@ function cachedMessageBody(msg: Message): Node {
190
198
  }
191
199
 
192
200
  function conversationMessageNode(msg: Message): Node {
201
+ const label = msg.role === "toolResult" ? `${msg.toolName} result` : msg.role;
193
202
  return VStack({ gap: 1 }, [
194
203
  cachedMessageBody(msg),
195
204
  HStack({ gap: 1, justifyContent: "end" }, [
@@ -197,7 +206,7 @@ function conversationMessageNode(msg: Message): Node {
197
206
  fgColor: theme.bblack,
198
207
  italic: true,
199
208
  }),
200
- TextPill(msg.role, theme.bwhite, theme.bblack),
209
+ TextPill(label, theme.bwhite, theme.bblack),
201
210
  ]),
202
211
  ]);
203
212
  }
@@ -249,7 +258,7 @@ export function Conversation(state: TUIState) {
249
258
  return VStack(
250
259
  {
251
260
  flex: 1,
252
- gap: 1,
261
+ gap: 3,
253
262
  overflow: "scroll",
254
263
  scrollOffset: state.stickToBottom ? Infinity : state.scrollOffset,
255
264
  onScroll(offset, maxOffset) {
@@ -1,6 +1,6 @@
1
1
  import { HStack, Text, TextInput, VStack } from "@cel-tui/core";
2
- import { getModels, type ThinkingLevel } from "@mariozechner/pi-ai";
3
- import { getOAuthProviders } from "@mariozechner/pi-ai/oauth";
2
+ import { getModels, type ThinkingLevel } from "@earendil-works/pi-ai";
3
+ import { getOAuthProviders } from "@earendil-works/pi-ai/oauth";
4
4
  import { saveSettings } from "./args";
5
5
  import { getAvailableProviders } from "./oauth";
6
6
  import { listSessionsForCwd } from "./session";
package/src/tui.ts CHANGED
@@ -11,7 +11,7 @@ import { updateSession } from "./session";
11
11
  import { estimateTokens, secureRandomString } from "./shared";
12
12
  import { bash, runBashTool } from "./tool-bash";
13
13
  import { edit, runEditTool } from "./tool-edit";
14
- import { runTaskTool, task } from "./tool-task";
14
+ import { read, runReadTool } from "./tool-read";
15
15
  import {
16
16
  ActivityPill,
17
17
  ContextPill,
@@ -43,7 +43,6 @@ function clearOrAbort(state: TUIState) {
43
43
 
44
44
  export function initTUI(state: TUIState, leave: (s: string) => void) {
45
45
  // TODO: Cleanup accumulated sessions for this cwd.
46
-
47
46
  const { spinnerEvery, currentSpinner } = Spinner();
48
47
 
49
48
  // Stable 60fps rendering.
@@ -147,10 +146,7 @@ async function streamAgentTUI(state: TUIState) {
147
146
  const tools: ToolAndRunner[] = [
148
147
  { tool: bash, runner: runBashTool },
149
148
  { tool: edit, runner: runEditTool },
150
- {
151
- tool: task,
152
- runner: (args, signal) => runTaskTool(state.options, args, signal),
153
- },
149
+ { tool: read, runner: runReadTool },
154
150
  ];
155
151
 
156
152
  let userContent = state.prompt;
package/src/types.ts CHANGED
@@ -8,8 +8,8 @@ import {
8
8
  type Tool,
9
9
  type ToolResultMessage,
10
10
  Type,
11
- } from "@mariozechner/pi-ai";
12
- import type { OAuthCredentials } from "@mariozechner/pi-ai/oauth";
11
+ } from "@earendil-works/pi-ai";
12
+ import type { OAuthCredentials } from "@earendil-works/pi-ai/oauth";
13
13
 
14
14
  const ThinkingLevelSchema = Type.Unsafe<ThinkingLevel>(
15
15
  Type.Union([
@@ -129,7 +129,11 @@ export type AgentToolEvent =
129
129
 
130
130
  export type ToolRunnerEvent =
131
131
  | { type: "output"; text: string }
132
- | { type: "result"; text: string };
132
+ | {
133
+ type: "result";
134
+ text: string;
135
+ image?: { data: string; mimeType: string };
136
+ };
133
137
 
134
138
  export type ToolAndRunner = {
135
139
  tool: Tool;