mini-coder 0.5.12 → 0.5.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/src/tool-shell.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  import type { Static, Tool } from "@mariozechner/pi-ai";
8
8
  import { Type } from "@mariozechner/pi-ai";
9
9
  import type { ToolHandler, ToolUpdateCallback } from "./agent.ts";
10
+ import { readFiniteNumber, readString, toRecord } from "./shared.ts";
10
11
  import {
11
12
  detectLineEnding,
12
13
  normalizeLineEndings,
@@ -34,6 +35,16 @@ export interface ShellOpts {
34
35
  onUpdate?: ToolUpdateCallback;
35
36
  }
36
37
 
38
+ /** Structured shell result preserved on tool-result messages and events. */
39
+ export interface ShellResultDetails {
40
+ /** Captured stdout text after shell-tool truncation. */
41
+ stdout: string;
42
+ /** Captured stderr text after shell-tool truncation. */
43
+ stderr: string;
44
+ /** Process exit code. */
45
+ exitCode: number;
46
+ }
47
+
37
48
  /** pi-ai tool definition for `shell`. */
38
49
  export const shellTool: Tool<typeof shellToolParameters> = {
39
50
  name: "shell",
@@ -65,21 +76,136 @@ const DEFAULT_MAX_LINES = 1000;
65
76
  const DEFAULT_MAX_BYTES = 50_000;
66
77
  const SHELL_UPDATE_INTERVAL_MS = 75;
67
78
  const SHELL_STREAM_DRAIN_TIMEOUT_MS = 25;
79
+ const LEGACY_SHELL_STDERR_PREFIX = "[stderr]\n";
80
+ const LEGACY_SHELL_STDERR_SEPARATOR = `\n\n${LEGACY_SHELL_STDERR_PREFIX}`;
68
81
 
69
- /** Format combined stdout/stderr for display in tool results. */
82
+ /** Format combined stdout/stderr for the legacy text payload preserved for model context. */
70
83
  function formatShellOutput(stdout: string, stderr: string): string {
71
84
  if (stdout && stderr) {
72
- return `${stdout}\n\n[stderr]\n${stderr}`;
85
+ return `${stdout}${LEGACY_SHELL_STDERR_SEPARATOR}${stderr}`;
73
86
  }
74
87
  if (stdout) {
75
88
  return stdout;
76
89
  }
77
90
  if (stderr) {
78
- return `[stderr]\n${stderr}`;
91
+ return `${LEGACY_SHELL_STDERR_PREFIX}${stderr}`;
79
92
  }
80
93
  return "";
81
94
  }
82
95
 
96
+ /** Format the shell result as the legacy text payload stored in tool content. */
97
+ export function formatShellResultText(result: ShellResultDetails): string {
98
+ const body = formatShellOutput(result.stdout, result.stderr) || "(no output)";
99
+ return `Exit code: ${result.exitCode}\n${body}`;
100
+ }
101
+
102
+ /** Parse structured shell-result details from a persisted tool-result message. */
103
+ export function parseShellResultDetails(
104
+ details: unknown,
105
+ ): ShellResultDetails | null {
106
+ const record = toRecord(details);
107
+ if (!record) {
108
+ return null;
109
+ }
110
+
111
+ const stdout = readString(record, "stdout");
112
+ const stderr = readString(record, "stderr");
113
+ const exitCode = readFiniteNumber(record, "exitCode");
114
+ if (stdout === null || stderr === null || exitCode === null) {
115
+ return null;
116
+ }
117
+
118
+ return { stdout, stderr, exitCode };
119
+ }
120
+
121
+ /** Parse the legacy flattened shell-result text stored by older builds. */
122
+ export function parseLegacyShellResult(
123
+ text: string,
124
+ ): ShellResultDetails | null {
125
+ const match = /^Exit code: (\d+)(?:\n([\s\S]*))?$/.exec(
126
+ normalizeLineEndings(text, "\n"),
127
+ );
128
+ if (!match) {
129
+ return null;
130
+ }
131
+
132
+ const exitCodeText = match[1];
133
+ if (!exitCodeText) {
134
+ return null;
135
+ }
136
+
137
+ const exitCode = Number.parseInt(exitCodeText, 10);
138
+ const body = match[2] ?? "";
139
+ if (body === "" || body === "(no output)") {
140
+ return { stdout: "", stderr: "", exitCode };
141
+ }
142
+ if (body.startsWith(LEGACY_SHELL_STDERR_PREFIX)) {
143
+ return {
144
+ stdout: "",
145
+ stderr: body.slice(LEGACY_SHELL_STDERR_PREFIX.length),
146
+ exitCode,
147
+ };
148
+ }
149
+
150
+ const separatorIndex = body.indexOf(LEGACY_SHELL_STDERR_SEPARATOR);
151
+ if (separatorIndex === -1) {
152
+ return { stdout: body, stderr: "", exitCode };
153
+ }
154
+
155
+ return {
156
+ stdout: body.slice(0, separatorIndex),
157
+ stderr: body.slice(separatorIndex + LEGACY_SHELL_STDERR_SEPARATOR.length),
158
+ exitCode,
159
+ };
160
+ }
161
+
162
+ function truncateShellResult(
163
+ result: ShellResultDetails,
164
+ maxLines: number,
165
+ maxBytes: number,
166
+ ): ShellResultDetails {
167
+ if (result.stdout === "" || result.stderr === "") {
168
+ return {
169
+ stdout:
170
+ result.stdout === ""
171
+ ? ""
172
+ : truncateOutput(result.stdout, maxLines, maxBytes),
173
+ stderr:
174
+ result.stderr === ""
175
+ ? ""
176
+ : truncateOutput(result.stderr, maxLines, maxBytes),
177
+ exitCode: result.exitCode,
178
+ };
179
+ }
180
+
181
+ return {
182
+ stdout: truncateOutput(
183
+ result.stdout,
184
+ Math.max(1, Math.ceil(maxLines / 2)),
185
+ Math.max(1, Math.ceil(maxBytes / 2)),
186
+ ),
187
+ stderr: truncateOutput(
188
+ result.stderr,
189
+ Math.max(1, Math.floor(maxLines / 2)),
190
+ Math.max(1, Math.floor(maxBytes / 2)),
191
+ ),
192
+ exitCode: result.exitCode,
193
+ };
194
+ }
195
+
196
+ function buildShellToolResult(
197
+ result: ShellResultDetails,
198
+ maxLines: number,
199
+ maxBytes: number,
200
+ ): ToolExecResult {
201
+ const truncated = truncateShellResult(result, maxLines, maxBytes);
202
+ return {
203
+ content: [{ type: "text", text: formatShellResultText(truncated) }],
204
+ details: truncated,
205
+ isError: truncated.exitCode !== 0,
206
+ };
207
+ }
208
+
83
209
  interface ShellCommandLines {
84
210
  lines: string[];
85
211
  lineEnding: "\n" | "\r\n";
@@ -648,9 +774,15 @@ export async function executeShell(
648
774
  opts.onUpdate(textResult(output, false));
649
775
  }
650
776
 
651
- const isError = exitCode !== 0;
652
- const body = output || "(no output)";
653
- return textResult(`Exit code: ${exitCode}\n${body}`, isError);
777
+ return buildShellToolResult(
778
+ {
779
+ stdout: stdoutCapture?.getOutput().trimEnd() ?? "",
780
+ stderr: stderrCapture?.getOutput().trimEnd() ?? "",
781
+ exitCode,
782
+ },
783
+ maxLines,
784
+ maxBytes,
785
+ );
654
786
  } catch (err) {
655
787
  cleanupAbort?.();
656
788
  cleanupAbort = null;
package/src/tools.ts CHANGED
@@ -59,8 +59,12 @@ export {
59
59
  } from "./tool-read.ts";
60
60
  export {
61
61
  executeShell,
62
+ formatShellResultText,
63
+ parseLegacyShellResult,
64
+ parseShellResultDetails,
62
65
  type ShellArgs,
63
66
  type ShellOpts,
67
+ type ShellResultDetails,
64
68
  shellTool,
65
69
  shellToolHandler,
66
70
  truncateOutput,
package/src/ui/agent.ts CHANGED
@@ -46,6 +46,8 @@ interface UiAgentRuntime {
46
46
  requestRender: (priority?: UiRenderPriority) => void;
47
47
  /** Re-enable stick-to-bottom behavior for the conversation log. */
48
48
  scrollConversationToBottom: () => void;
49
+ /** Clear the readonly queued-input draft after it is committed. */
50
+ clearQueuedInputDraft: () => void;
49
51
  /** Start the active-turn divider animation. */
50
52
  startDividerAnimation: () => void;
51
53
  /** Stop the active-turn divider animation. */
@@ -116,12 +118,14 @@ export function createUiAgentController(
116
118
  if (
117
119
  pending.toolName === event.name &&
118
120
  pending.content === event.result.content &&
121
+ pending.details === event.result.details &&
119
122
  pending.isError === event.result.isError
120
123
  ) {
121
124
  return false;
122
125
  }
123
126
  pending.toolName = event.name;
124
127
  pending.content = event.result.content;
128
+ pending.details = event.result.details;
125
129
  pending.isError = event.result.isError;
126
130
  return true;
127
131
  }
@@ -130,6 +134,7 @@ export function createUiAgentController(
130
134
  toolCallId: event.toolCallId,
131
135
  toolName: event.name,
132
136
  content: event.result.content,
137
+ details: event.result.details,
133
138
  isError: event.result.isError,
134
139
  });
135
140
  return true;
@@ -195,6 +200,7 @@ export function createUiAgentController(
195
200
  ): void => {
196
201
  switch (event.type) {
197
202
  case "user_message":
203
+ runtime.clearQueuedInputDraft();
198
204
  runtime.scrollConversationToBottom();
199
205
  runtime.requestRender("normal");
200
206
  return;
@@ -280,6 +286,7 @@ export function createUiAgentController(
280
286
  onEvent: (event, currentState) => handleAgentEvent(event, currentState),
281
287
  onTurnEnd: () => {
282
288
  resetStreamingState();
289
+ runtime.clearQueuedInputDraft();
283
290
  runtime.stopDividerAnimation();
284
291
  runtime.requestRender("normal");
285
292
  },