mini-coder 0.5.10 → 0.5.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/BENCHMARK.md +408 -0
- package/PROGRESS.md +5 -0
- package/README.md +14 -1
- package/assets/mc-claude-smart.png +0 -0
- package/assets/mc-gpt-smart.png +0 -0
- package/benchmark-loop.sh +19 -0
- package/package.json +1 -1
- package/src/agent.ts +5 -1
- package/src/headless.ts +62 -21
- package/src/index.ts +35 -56
- package/src/prompt.ts +8 -0
- package/src/session-message.ts +393 -0
- package/src/session.ts +102 -396
- package/src/settings.ts +19 -15
- package/src/shared.ts +39 -0
- package/src/submit.ts +3 -25
- package/src/text.ts +71 -0
- package/src/tool-common.ts +91 -0
- package/src/tool-grep.ts +606 -0
- package/src/tool-read.ts +313 -0
- package/src/tool-shell.ts +869 -0
- package/src/tools.ts +186 -995
- package/src/ui/agent.ts +199 -110
- package/src/ui/commands.test.ts +16 -302
- package/src/ui/commands.ts +21 -47
- package/src/ui/conversation.test.ts +263 -1389
- package/src/ui/conversation.ts +496 -151
- package/src/ui/input.test.ts +1 -43
- package/src/ui/runtime.ts +69 -0
- package/src/ui.ts +196 -114
- package/src/ui/agent.test.ts +0 -49
- package/src/ui/help.test.ts +0 -65
- package/src/ui/overlay.test.ts +0 -42
- package/src/ui/render-performance.test.ts +0 -444
- package/src/ui/status.test.ts +0 -489
package/src/ui/agent.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Input parsing, message submission, and streaming agent-event handling for the terminal UI.
|
|
3
3
|
*
|
|
4
|
-
* This module
|
|
5
|
-
*
|
|
6
|
-
* {@link AppState}; transient UI
|
|
7
|
-
* `ui.ts` can stay a small
|
|
4
|
+
* This module builds the controller used by `ui.ts`. Each controller owns its
|
|
5
|
+
* own transient streaming render state for the in-progress assistant response.
|
|
6
|
+
* Persistent session and message state remain on {@link AppState}; transient UI
|
|
7
|
+
* concerns are exposed through runtime hooks so `ui.ts` can stay a small
|
|
8
|
+
* orchestrator.
|
|
8
9
|
*
|
|
9
10
|
* @module
|
|
10
11
|
*/
|
|
@@ -22,17 +23,18 @@ import type {
|
|
|
22
23
|
PendingToolResult,
|
|
23
24
|
StreamingConversationState,
|
|
24
25
|
} from "./conversation.ts";
|
|
26
|
+
import type { UiRenderPriority } from "./runtime.ts";
|
|
25
27
|
|
|
26
28
|
export { isEmptyUserContent, stripSkillFrontmatter } from "../submit.ts";
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
interface UiAgentRuntimeState {
|
|
31
|
+
/** Whether a response is currently streaming. */
|
|
32
|
+
isStreaming: boolean;
|
|
33
|
+
/** Streaming assistant content for the current response. */
|
|
34
|
+
content: AssistantMessage["content"];
|
|
35
|
+
/** Tool results collected during the current streaming turn. */
|
|
36
|
+
pendingToolResults: PendingToolResult[];
|
|
37
|
+
}
|
|
36
38
|
|
|
37
39
|
/** Hooks implemented by `ui.ts` to bridge controller logic with runtime UI state. */
|
|
38
40
|
interface UiAgentRuntime {
|
|
@@ -40,8 +42,8 @@ interface UiAgentRuntime {
|
|
|
40
42
|
appendInfoMessage: (text: string, state: AppState) => void;
|
|
41
43
|
/** Dispatch a parsed slash command. */
|
|
42
44
|
handleCommand: (command: string, state: AppState) => boolean;
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
+
/** Schedule a UI render. */
|
|
46
|
+
requestRender: (priority?: UiRenderPriority) => void;
|
|
45
47
|
/** Re-enable stick-to-bottom behavior for the conversation log. */
|
|
46
48
|
scrollConversationToBottom: () => void;
|
|
47
49
|
/** Start the active-turn divider animation. */
|
|
@@ -54,30 +56,10 @@ interface UiAgentRuntime {
|
|
|
54
56
|
interface UiAgentController {
|
|
55
57
|
/** Route raw user input through parseInput and dispatch accordingly. */
|
|
56
58
|
handleInput: (raw: string, state: AppState) => void;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
*
|
|
62
|
-
* Used by `resetUiState()` and tests to keep state isolated.
|
|
63
|
-
*/
|
|
64
|
-
export function resetUiAgentState(): void {
|
|
65
|
-
streamingContent = [];
|
|
66
|
-
isStreaming = false;
|
|
67
|
-
pendingToolResults = [];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Read the current streaming tail state for conversation rendering.
|
|
72
|
-
*
|
|
73
|
-
* @returns The in-progress assistant content and pending tool results.
|
|
74
|
-
*/
|
|
75
|
-
export function getStreamingConversationState(): StreamingConversationState {
|
|
76
|
-
return {
|
|
77
|
-
isStreaming,
|
|
78
|
-
content: streamingContent,
|
|
79
|
-
pendingToolResults,
|
|
80
|
-
};
|
|
59
|
+
/** Read the current streaming tail state for conversation rendering. */
|
|
60
|
+
getStreamingConversationState: () => StreamingConversationState;
|
|
61
|
+
/** Reset the transient streaming state owned by this controller. */
|
|
62
|
+
reset: () => void;
|
|
81
63
|
}
|
|
82
64
|
|
|
83
65
|
/**
|
|
@@ -89,6 +71,173 @@ export function getStreamingConversationState(): StreamingConversationState {
|
|
|
89
71
|
export function createUiAgentController(
|
|
90
72
|
runtime: UiAgentRuntime,
|
|
91
73
|
): UiAgentController {
|
|
74
|
+
const streamingState: UiAgentRuntimeState = {
|
|
75
|
+
isStreaming: false,
|
|
76
|
+
content: [],
|
|
77
|
+
pendingToolResults: [],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const resetStreamingState = (): boolean => {
|
|
81
|
+
const changed =
|
|
82
|
+
streamingState.isStreaming ||
|
|
83
|
+
streamingState.content.length > 0 ||
|
|
84
|
+
streamingState.pendingToolResults.length > 0;
|
|
85
|
+
streamingState.isStreaming = false;
|
|
86
|
+
streamingState.content = [];
|
|
87
|
+
streamingState.pendingToolResults = [];
|
|
88
|
+
return changed;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const clearStreamingContent = (): boolean => {
|
|
92
|
+
if (streamingState.content.length === 0) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
streamingState.content = [];
|
|
96
|
+
return true;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const setStreamingContent = (
|
|
100
|
+
content: AssistantMessage["content"],
|
|
101
|
+
): boolean => {
|
|
102
|
+
if (streamingState.content === content) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
streamingState.content = content;
|
|
106
|
+
return true;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const upsertPendingToolResult = (
|
|
110
|
+
event: Extract<AgentEvent, { type: "tool_delta" | "tool_end" }>,
|
|
111
|
+
): boolean => {
|
|
112
|
+
const pending = streamingState.pendingToolResults.find(
|
|
113
|
+
(toolResult) => toolResult.toolCallId === event.toolCallId,
|
|
114
|
+
);
|
|
115
|
+
if (pending) {
|
|
116
|
+
if (
|
|
117
|
+
pending.toolName === event.name &&
|
|
118
|
+
pending.content === event.result.content &&
|
|
119
|
+
pending.isError === event.result.isError
|
|
120
|
+
) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
pending.toolName = event.name;
|
|
124
|
+
pending.content = event.result.content;
|
|
125
|
+
pending.isError = event.result.isError;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
streamingState.pendingToolResults.push({
|
|
130
|
+
toolCallId: event.toolCallId,
|
|
131
|
+
toolName: event.name,
|
|
132
|
+
content: event.result.content,
|
|
133
|
+
isError: event.result.isError,
|
|
134
|
+
});
|
|
135
|
+
return true;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const removePendingToolResult = (toolCallId: string): boolean => {
|
|
139
|
+
const nextPendingToolResults = streamingState.pendingToolResults.filter(
|
|
140
|
+
(toolResult) => toolResult.toolCallId !== toolCallId,
|
|
141
|
+
);
|
|
142
|
+
if (
|
|
143
|
+
nextPendingToolResults.length === streamingState.pendingToolResults.length
|
|
144
|
+
) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
streamingState.pendingToolResults = nextPendingToolResults;
|
|
148
|
+
return true;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const isStreamingContentEvent = (
|
|
152
|
+
event: AgentEvent,
|
|
153
|
+
): event is Extract<
|
|
154
|
+
AgentEvent,
|
|
155
|
+
{
|
|
156
|
+
type:
|
|
157
|
+
| "text_delta"
|
|
158
|
+
| "thinking_delta"
|
|
159
|
+
| "toolcall_start"
|
|
160
|
+
| "toolcall_delta"
|
|
161
|
+
| "toolcall_end";
|
|
162
|
+
}
|
|
163
|
+
> => {
|
|
164
|
+
return (
|
|
165
|
+
event.type === "text_delta" ||
|
|
166
|
+
event.type === "thinking_delta" ||
|
|
167
|
+
event.type === "toolcall_start" ||
|
|
168
|
+
event.type === "toolcall_delta" ||
|
|
169
|
+
event.type === "toolcall_end"
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const isPendingToolProgressEvent = (
|
|
174
|
+
event: AgentEvent,
|
|
175
|
+
): event is Extract<AgentEvent, { type: "tool_delta" | "tool_end" }> => {
|
|
176
|
+
return event.type === "tool_delta" || event.type === "tool_end";
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const handleCommittedAgentEvent = (
|
|
180
|
+
event: Exclude<
|
|
181
|
+
AgentEvent,
|
|
182
|
+
| Extract<
|
|
183
|
+
AgentEvent,
|
|
184
|
+
{
|
|
185
|
+
type:
|
|
186
|
+
| "text_delta"
|
|
187
|
+
| "thinking_delta"
|
|
188
|
+
| "toolcall_start"
|
|
189
|
+
| "toolcall_delta"
|
|
190
|
+
| "toolcall_end";
|
|
191
|
+
}
|
|
192
|
+
>
|
|
193
|
+
| Extract<AgentEvent, { type: "tool_delta" | "tool_end" }>
|
|
194
|
+
>,
|
|
195
|
+
): void => {
|
|
196
|
+
switch (event.type) {
|
|
197
|
+
case "user_message":
|
|
198
|
+
runtime.scrollConversationToBottom();
|
|
199
|
+
runtime.requestRender("normal");
|
|
200
|
+
return;
|
|
201
|
+
case "assistant_message":
|
|
202
|
+
if (clearStreamingContent()) {
|
|
203
|
+
runtime.requestRender("normal");
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
case "tool_result":
|
|
207
|
+
if (removePendingToolResult(event.message.toolCallId)) {
|
|
208
|
+
runtime.requestRender("normal");
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
case "done":
|
|
212
|
+
case "error":
|
|
213
|
+
case "aborted":
|
|
214
|
+
if (resetStreamingState()) {
|
|
215
|
+
runtime.requestRender("normal");
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
case "tool_start":
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const handleAgentEvent = (event: AgentEvent, _state: AppState): void => {
|
|
224
|
+
if (isStreamingContentEvent(event)) {
|
|
225
|
+
if (setStreamingContent(event.content)) {
|
|
226
|
+
runtime.requestRender("stream");
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (isPendingToolProgressEvent(event)) {
|
|
232
|
+
if (upsertPendingToolResult(event)) {
|
|
233
|
+
runtime.requestRender("stream");
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
handleCommittedAgentEvent(event);
|
|
239
|
+
};
|
|
240
|
+
|
|
92
241
|
const submitMessageAsync = (rawInput: string, state: AppState): void => {
|
|
93
242
|
const resolved = resolveRawInput(rawInput, state);
|
|
94
243
|
|
|
@@ -120,20 +269,19 @@ export function createUiAgentController(
|
|
|
120
269
|
submitPromise = submitResolvedInput(rawInput, resolved.content, state, {
|
|
121
270
|
onUserMessage: () => {
|
|
122
271
|
runtime.scrollConversationToBottom();
|
|
123
|
-
runtime.
|
|
272
|
+
runtime.requestRender("normal");
|
|
124
273
|
},
|
|
125
274
|
onTurnStart: () => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
pendingToolResults = [];
|
|
275
|
+
resetStreamingState();
|
|
276
|
+
streamingState.isStreaming = true;
|
|
129
277
|
runtime.startDividerAnimation();
|
|
130
|
-
runtime.
|
|
278
|
+
runtime.requestRender("normal");
|
|
131
279
|
},
|
|
132
280
|
onEvent: (event, currentState) => handleAgentEvent(event, currentState),
|
|
133
281
|
onTurnEnd: () => {
|
|
134
|
-
|
|
282
|
+
resetStreamingState();
|
|
135
283
|
runtime.stopDividerAnimation();
|
|
136
|
-
runtime.
|
|
284
|
+
runtime.requestRender("normal");
|
|
137
285
|
},
|
|
138
286
|
})
|
|
139
287
|
.then(() => undefined)
|
|
@@ -152,70 +300,11 @@ export function createUiAgentController(
|
|
|
152
300
|
state.activeTurnPromise = submitPromise;
|
|
153
301
|
};
|
|
154
302
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
case "toolcall_end":
|
|
162
|
-
streamingContent = event.content;
|
|
163
|
-
runtime.render();
|
|
164
|
-
break;
|
|
165
|
-
|
|
166
|
-
case "user_message":
|
|
167
|
-
runtime.scrollConversationToBottom();
|
|
168
|
-
runtime.render();
|
|
169
|
-
break;
|
|
170
|
-
|
|
171
|
-
case "assistant_message":
|
|
172
|
-
streamingContent = [];
|
|
173
|
-
runtime.render();
|
|
174
|
-
break;
|
|
175
|
-
|
|
176
|
-
case "tool_start":
|
|
177
|
-
break;
|
|
178
|
-
|
|
179
|
-
case "tool_delta":
|
|
180
|
-
case "tool_end": {
|
|
181
|
-
const pending = pendingToolResults.find(
|
|
182
|
-
(toolResult) => toolResult.toolCallId === event.toolCallId,
|
|
183
|
-
);
|
|
184
|
-
if (pending) {
|
|
185
|
-
pending.toolName = event.name;
|
|
186
|
-
pending.content = event.result.content;
|
|
187
|
-
pending.isError = event.result.isError;
|
|
188
|
-
} else {
|
|
189
|
-
pendingToolResults.push({
|
|
190
|
-
toolCallId: event.toolCallId,
|
|
191
|
-
toolName: event.name,
|
|
192
|
-
content: event.result.content,
|
|
193
|
-
isError: event.result.isError,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
runtime.render();
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
case "tool_result":
|
|
201
|
-
pendingToolResults = pendingToolResults.filter(
|
|
202
|
-
(toolResult) => toolResult.toolCallId !== event.message.toolCallId,
|
|
203
|
-
);
|
|
204
|
-
runtime.render();
|
|
205
|
-
break;
|
|
206
|
-
|
|
207
|
-
case "done":
|
|
208
|
-
case "error":
|
|
209
|
-
case "aborted":
|
|
210
|
-
resetUiAgentState();
|
|
211
|
-
runtime.render();
|
|
212
|
-
break;
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const handleInput = (raw: string, state: AppState): void => {
|
|
217
|
-
submitMessageAsync(raw, state);
|
|
303
|
+
return {
|
|
304
|
+
handleInput: (raw, state) => {
|
|
305
|
+
submitMessageAsync(raw, state);
|
|
306
|
+
},
|
|
307
|
+
getStreamingConversationState: () => streamingState,
|
|
308
|
+
reset: resetStreamingState,
|
|
218
309
|
};
|
|
219
|
-
|
|
220
|
-
return { handleInput };
|
|
221
310
|
}
|