@iinm/plain-agent 1.10.10 → 1.10.12

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.10.10",
3
+ "version": "1.10.12",
4
4
  "description": "A lightweight CLI-based coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/agent.d.ts CHANGED
@@ -37,7 +37,7 @@ type AgentEventMap = {
37
37
  message: [Message];
38
38
  partialMessageContent: [PartialMessageContent];
39
39
  error: [Error];
40
- toolUseRequest: [];
40
+ toolUseRequest: [number];
41
41
  turnEnd: [];
42
42
  providerTokenUsage: [ProviderTokenUsage];
43
43
  subagentSwitched: [{ name: string } | null];
package/src/agentLoop.mjs CHANGED
@@ -172,9 +172,6 @@ export function createAgentLoop({
172
172
  content: validation.toolResults,
173
173
  },
174
174
  ]);
175
- if (validation.errorMessage) {
176
- console.error(styleText("yellow", validation.errorMessage));
177
- }
178
175
  continue;
179
176
  }
180
177
 
@@ -205,7 +202,7 @@ export function createAgentLoop({
205
202
 
206
203
  const isAllToolUseApproved = decisions.every((d) => d.action === "allow");
207
204
  if (!isAllToolUseApproved) {
208
- agentEventEmitter.emit("toolUseRequest");
205
+ agentEventEmitter.emit("toolUseRequest", toolUseParts.length);
209
206
  break;
210
207
  }
211
208
 
@@ -70,7 +70,7 @@ export async function formatToolUse(toolUse) {
70
70
  /** @type {Partial<ExecCommandInput>} */
71
71
  const execCommandInput = input;
72
72
  return [
73
- `tool: ${toolName}`,
73
+ `${toolName}`,
74
74
  `command: ${JSON.stringify(execCommandInput.command)}`,
75
75
  formatArgs(execCommandInput.args),
76
76
  ].join("\n");
@@ -80,7 +80,7 @@ export async function formatToolUse(toolUse) {
80
80
  /** @type {Partial<WriteFileInput>} */
81
81
  const writeFileInput = input;
82
82
  return [
83
- `tool: ${toolName}`,
83
+ `${toolName}`,
84
84
  `filePath: ${writeFileInput.filePath}`,
85
85
  `content:\n${writeFileInput.content}`,
86
86
  ].join("\n");
@@ -92,18 +92,16 @@ export async function formatToolUse(toolUse) {
92
92
  const filePath = patchFileInput.filePath ?? "";
93
93
  const patch = patchFileInput.patch || "";
94
94
  const rendered = await renderPatch(filePath, patch);
95
- return [
96
- `tool: ${toolName}`,
97
- `path: ${filePath}`,
98
- `patch:\n${rendered}`,
99
- ].join("\n");
95
+ return [`${toolName}`, `path: ${filePath}`, `patch:\n${rendered}`].join(
96
+ "\n",
97
+ );
100
98
  }
101
99
 
102
100
  if (toolName === "read_file") {
103
101
  /** @type {Partial<ReadFileInput>} */
104
102
  const readFileInput = input;
105
103
  /** @type {string[]} */
106
- const lines = [`tool: ${toolName}`, `filePath: ${readFileInput.filePath}`];
104
+ const lines = [`${toolName}`, `filePath: ${readFileInput.filePath}`];
107
105
  if (readFileInput.offset !== undefined) {
108
106
  lines.push(`offset: ${readFileInput.offset}`);
109
107
  }
@@ -117,7 +115,7 @@ export async function formatToolUse(toolUse) {
117
115
  /** @type {Partial<TmuxCommandInput>} */
118
116
  const tmuxCommandInput = input;
119
117
  return [
120
- `tool: ${toolName}`,
118
+ `${toolName}`,
121
119
  `command: ${tmuxCommandInput.command}`,
122
120
  formatArgs(tmuxCommandInput.args),
123
121
  ].join("\n");
@@ -127,7 +125,7 @@ export async function formatToolUse(toolUse) {
127
125
  /** @type {Partial<SwitchToSubagentInput>} */
128
126
  const switchToSubagentInput = input;
129
127
  return [
130
- `tool: ${toolName}`,
128
+ `${toolName}`,
131
129
  `name: ${switchToSubagentInput.name}`,
132
130
  `goal: ${switchToSubagentInput.goal}`,
133
131
  ].join("\n");
@@ -137,7 +135,7 @@ export async function formatToolUse(toolUse) {
137
135
  /** @type {Partial<CompactContextInput>} */
138
136
  const compactContextInput = input;
139
137
  return [
140
- `tool: ${toolName}`,
138
+ `${toolName}`,
141
139
  `memoryPath: ${compactContextInput.memoryPath}`,
142
140
  `reason: ${compactContextInput.reason}`,
143
141
  ].join("\n");
@@ -147,7 +145,7 @@ export async function formatToolUse(toolUse) {
147
145
  /** @type {Partial<import("../tools/switchToMainAgent").SwitchToMainAgentInput>} */
148
146
  const switchToMainAgentInput = input;
149
147
  return [
150
- `tool: ${toolName}`,
148
+ `${toolName}`,
151
149
  `memoryPath: ${switchToMainAgentInput.memoryPath}`,
152
150
  ].join("\n");
153
151
  }
@@ -159,7 +157,7 @@ export async function formatToolUse(toolUse) {
159
157
  ? webSearchInput.searches.map((s) => s.keywords.join(" ")).join(" | ")
160
158
  : "";
161
159
  return [
162
- `tool: ${toolName}`,
160
+ `${toolName}`,
163
161
  `searches: ${searchesLine}`,
164
162
  `question: ${webSearchInput.question}`,
165
163
  ].join("\n");
@@ -169,7 +167,7 @@ export async function formatToolUse(toolUse) {
169
167
  /** @type {Partial<import("../tools/webFetch.mjs").WebFetchInput>} */
170
168
  const webFetchInput = input;
171
169
  return [
172
- `tool: ${toolName}`,
170
+ `${toolName}`,
173
171
  `url: ${webFetchInput.url}`,
174
172
  `question: ${webFetchInput.question}`,
175
173
  ].join("\n");
@@ -212,7 +210,7 @@ export function formatToolResult(toolResult) {
212
210
  const contentString = contentStringParts.join("\n\n");
213
211
 
214
212
  if (isError) {
215
- return styleText("red", contentString);
213
+ return styleText("magenta", contentString);
216
214
  }
217
215
 
218
216
  if (toolResult.toolName === "exec_command") {
@@ -223,7 +221,7 @@ export function formatToolResult(toolResult) {
223
221
  styleText("yellow", "$1"),
224
222
  )
225
223
  .replace(/(^<stderr>|<\/stderr>$)/gm, styleText("magenta", "$1"))
226
- .replace(/(^<error>|<\/error>$)/gm, styleText("red", "$1"));
224
+ .replace(/(^<error>|<\/error>$)/gm, styleText("magenta", "$1"));
227
225
  }
228
226
 
229
227
  if (toolResult.toolName === "read_file") {
@@ -237,7 +235,7 @@ export function formatToolResult(toolResult) {
237
235
  return contentString
238
236
  .replace(/(^<stdout>|<\/stdout>$)/gm, styleText("blue", "$1"))
239
237
  .replace(/(^<stderr>|<\/stderr>$)/gm, styleText("magenta", "$1"))
240
- .replace(/(^<error>|<\/error>$)/gm, styleText("red", "$1"))
238
+ .replace(/(^<error>|<\/error>$)/gm, styleText("magenta", "$1"))
241
239
  .replace(/(^<tmux:.*?>|<\/tmux:.*?>$)/gm, styleText("green", "$1"));
242
240
  }
243
241
 
@@ -858,7 +856,7 @@ function renderPatchBlock(block, originalLines, nonce) {
858
856
  // "+ ".
859
857
  for (const op of diffLines(oldSlice, block.body)) {
860
858
  if (op.type === "-") {
861
- out.push(styleText("red", `- ${op.line}`));
859
+ out.push(styleText("magenta", `- ${op.line}`));
862
860
  } else if (op.type === "+") {
863
861
  out.push(styleText("green", `+ ${op.line}`));
864
862
  } else {
@@ -501,9 +501,7 @@ export function startInteractiveSession({
501
501
  }
502
502
  if (partialContent.position === "stop") {
503
503
  if (partialContent.type === "tool_use") {
504
- process.stdout.write(
505
- `\r\x1b[K${styleText("gray", `<${partialContent.type}>`)}\n`,
506
- );
504
+ process.stdout.write("\r\x1b[K");
507
505
  } else {
508
506
  // Flush any buffered text before printing the closing tag
509
507
  streamBuffer.forceFlush();
@@ -524,13 +522,14 @@ export function startInteractiveSession({
524
522
  );
525
523
  });
526
524
 
527
- agentEventEmitter.on("toolUseRequest", () => {
525
+ agentEventEmitter.on("toolUseRequest", (toolCount) => {
526
+ const toolText = toolCount === 1 ? "tool call" : "tool calls";
528
527
  cli.setPrompt(
529
528
  getCliPrompt(
530
529
  state.subagentName,
531
530
  styleText(
532
531
  "yellow",
533
- "Approve tool calls? (y = allow once, Y = allow in this session, or feedback)",
532
+ `Approve ${toolCount} ${toolText}? (y = allow once, Y = allow in this session, or feedback)`,
534
533
  ),
535
534
  ),
536
535
  );
@@ -28,7 +28,7 @@ export function createCompactContextTool() {
28
28
  properties: {
29
29
  memoryPath: {
30
30
  type: "string",
31
- description: `Path to the memory file under ${AGENT_MEMORY_DIR}/.`,
31
+ description: "Path to the memory file.",
32
32
  },
33
33
  reason: {
34
34
  type: "string",
@@ -47,6 +47,7 @@ import { noThrow } from "../utils/noThrow.mjs";
47
47
  * @property {CallModel} modelCaller
48
48
  * @property {number=} maxLengthPerSearch Truncate each search's output to this many characters (default 50000).
49
49
  * @property {number=} maxTotalLength Truncate the combined output across searches to this many characters (default 200000).
50
+ * @property {number=} delayBetweenRequestsMs Delay between each search request in milliseconds (default 1000).
50
51
  */
51
52
 
52
53
  /**
@@ -69,6 +70,9 @@ const DEFAULT_MAX_TOTAL_LENGTH = 200_000;
69
70
  /** @type {number} */
70
71
  const DEFAULT_SEARCH_TIMEOUT_MS = 30_000;
71
72
 
73
+ /** @type {number} */
74
+ const DEFAULT_DELAY_BETWEEN_REQUESTS_MS = 1_000;
75
+
72
76
  /** @type {number} */
73
77
  const SEARCH_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
74
78
 
@@ -200,10 +204,17 @@ async function webSearchViaCommand(config, input) {
200
204
  const maxLengthPerSearch =
201
205
  config.maxLengthPerSearch ?? DEFAULT_MAX_LENGTH_PER_SEARCH;
202
206
  const maxTotalLength = config.maxTotalLength ?? DEFAULT_MAX_TOTAL_LENGTH;
207
+ const delayBetweenRequestsMs =
208
+ config.delayBetweenRequestsMs ?? DEFAULT_DELAY_BETWEEN_REQUESTS_MS;
203
209
 
204
210
  /** @type {{ keywords: string[], text: string, truncated: boolean, originalLength: number, error?: string }[]} */
205
211
  const results = [];
206
212
  for (const search of input.searches) {
213
+ if (delayBetweenRequestsMs > 0 && results.length > 0) {
214
+ await new Promise((resolve) =>
215
+ setTimeout(resolve, delayBetweenRequestsMs),
216
+ );
217
+ }
207
218
  try {
208
219
  const raw = await runSearchCommand(config, search.keywords);
209
220
  const { text, truncated, originalLength } = truncateText(