@iinm/plain-agent 1.10.11 → 1.10.13

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.11",
3
+ "version": "1.10.13",
4
4
  "description": "A lightweight CLI-based coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -7,6 +7,7 @@ SCRIPT_NAME=$(basename "${BASH_SOURCE[0]}")
7
7
 
8
8
  MOUNTABLE_SCRIPT_PATH="$HOME/.cache/$SCRIPT_NAME/$SCRIPT_NAME"
9
9
  CONTAINER_SCRIPT_PATH="/sandbox/bin/$SCRIPT_NAME"
10
+ PRESET_BASE_IMAGE="public.ecr.aws/docker/library/debian:stable-20260505-slim"
10
11
 
11
12
  help() {
12
13
  cat << HELP
@@ -851,8 +852,8 @@ setup_container_user() {
851
852
  }
852
853
 
853
854
  print_preset_dockerfile() {
855
+ echo "FROM $PRESET_BASE_IMAGE"
854
856
  cat << 'EOF'
855
- FROM public.ecr.aws/docker/library/debian:stable-slim
856
857
 
857
858
  RUN apt update \
858
859
  && apt install -y \
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 code=.+?>|<\/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,8 @@ 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
+ // Clear current line, move up one line, and clear that line too
505
+ process.stdout.write("\x1b[2K\x1b[1F\x1b[2K");
507
506
  } else {
508
507
  // Flush any buffered text before printing the closing tag
509
508
  streamBuffer.forceFlush();
@@ -524,13 +523,14 @@ export function startInteractiveSession({
524
523
  );
525
524
  });
526
525
 
527
- agentEventEmitter.on("toolUseRequest", () => {
526
+ agentEventEmitter.on("toolUseRequest", (toolCount) => {
527
+ const toolText = toolCount === 1 ? "tool call" : "tool calls";
528
528
  cli.setPrompt(
529
529
  getCliPrompt(
530
530
  state.subagentName,
531
531
  styleText(
532
532
  "yellow",
533
- "Approve tool calls? (y = allow once, Y = allow in this session, or feedback)",
533
+ `Approve ${toolCount} ${toolText}? (y = allow once, Y = allow in this session, or feedback)`,
534
534
  ),
535
535
  ),
536
536
  );
@@ -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",
@@ -140,19 +140,41 @@ export function createExecCommandTool(config) {
140
140
  : "<stderr></stderr>",
141
141
  ];
142
142
 
143
- if (err) {
144
- // rg: 何もマッチしない場合は exit status != 0 になるので無視
145
- const ignoreError = [command, ...(args || [])].includes("rg");
143
+ if (!stderr && err) {
144
+ // rg: exit status != 0 when no matches are found.
145
+ const ignoreError = ["rg"].includes(input.command);
146
146
  if (!ignoreError) {
147
- // err.message が長過ぎる場合は先頭を表示
148
- const errMessageTruncated = err.message.slice(
147
+ // mask sandbox details
148
+ const originalCommand = [
149
+ input.command,
150
+ ...(input.args ?? []),
151
+ ];
152
+ const sandboxedCommand = [command, ...(args ?? [])];
153
+ const sandboxStr = [
154
+ ...sandboxedCommand.slice(
155
+ 0,
156
+ sandboxedCommand.length - originalCommand.length,
157
+ ),
158
+ "",
159
+ ].join(" ");
160
+
161
+ const errMessageMasked = sandboxStr
162
+ ? err.message.replaceAll(sandboxStr, "")
163
+ : err.message;
164
+
165
+ const errMessageTruncated = errMessageMasked.slice(
149
166
  0,
150
167
  OUTPUT_TRUNCATED_LENGTH,
151
168
  );
152
169
  const isErrMessageTruncated =
153
- err.message.length > OUTPUT_MAX_LENGTH;
170
+ errMessageMasked.length > OUTPUT_TRUNCATED_LENGTH;
171
+
154
172
  result.push(
155
- `\n<error>\n${err.name}: ${errMessageTruncated}${isErrMessageTruncated ? "... (Message truncated)" : ""}</error>`,
173
+ [
174
+ "",
175
+ `<error code="${err.code}" killed="${err.killed}" signal="${err.signal}">`,
176
+ `${err.name}: ${errMessageTruncated}${isErrMessageTruncated ? "... (Message truncated)" : ""}</error>`,
177
+ ].join("\n"),
156
178
  );
157
179
  }
158
180
  }
@@ -108,7 +108,7 @@ export function createTmuxCommandTool(config) {
108
108
  ? `<stderr>\n${isStderrTruncated ? "(Output truncated) ..." : ""}${stderrTruncated}\n</stderr>`
109
109
  : "<stderr></stderr>",
110
110
  ];
111
- if (err) {
111
+ if (!stderr && err) {
112
112
  const errMessageTruncated = err.message.slice(
113
113
  0,
114
114
  OUTPUT_MAX_LENGTH,