@kirosnn/mosaic 0.71.0 → 0.73.0

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.
Files changed (75) hide show
  1. package/README.md +1 -5
  2. package/package.json +4 -2
  3. package/src/agent/Agent.ts +353 -131
  4. package/src/agent/context.ts +4 -4
  5. package/src/agent/prompts/systemPrompt.ts +15 -6
  6. package/src/agent/prompts/toolsPrompt.ts +75 -10
  7. package/src/agent/provider/anthropic.ts +100 -100
  8. package/src/agent/provider/google.ts +102 -102
  9. package/src/agent/provider/mistral.ts +95 -95
  10. package/src/agent/provider/ollama.ts +77 -60
  11. package/src/agent/provider/openai.ts +42 -38
  12. package/src/agent/provider/rateLimit.ts +178 -0
  13. package/src/agent/provider/xai.ts +99 -99
  14. package/src/agent/tools/definitions.ts +19 -9
  15. package/src/agent/tools/executor.ts +95 -85
  16. package/src/agent/tools/exploreExecutor.ts +8 -10
  17. package/src/agent/tools/grep.ts +30 -29
  18. package/src/agent/tools/question.ts +7 -1
  19. package/src/agent/types.ts +9 -8
  20. package/src/components/App.tsx +45 -45
  21. package/src/components/CustomInput.tsx +214 -36
  22. package/src/components/Main.tsx +1146 -954
  23. package/src/components/Setup.tsx +1 -1
  24. package/src/components/Welcome.tsx +1 -1
  25. package/src/components/main/ApprovalPanel.tsx +4 -3
  26. package/src/components/main/ChatPage.tsx +858 -675
  27. package/src/components/main/HomePage.tsx +53 -38
  28. package/src/components/main/QuestionPanel.tsx +52 -7
  29. package/src/components/main/ThinkingIndicator.tsx +2 -1
  30. package/src/index.tsx +50 -20
  31. package/src/mcp/approvalPolicy.ts +148 -0
  32. package/src/mcp/cli/add.ts +185 -0
  33. package/src/mcp/cli/doctor.ts +77 -0
  34. package/src/mcp/cli/index.ts +85 -0
  35. package/src/mcp/cli/list.ts +50 -0
  36. package/src/mcp/cli/logs.ts +24 -0
  37. package/src/mcp/cli/manage.ts +99 -0
  38. package/src/mcp/cli/show.ts +53 -0
  39. package/src/mcp/cli/tools.ts +77 -0
  40. package/src/mcp/config.ts +223 -0
  41. package/src/mcp/index.ts +80 -0
  42. package/src/mcp/processManager.ts +299 -0
  43. package/src/mcp/rateLimiter.ts +50 -0
  44. package/src/mcp/registry.ts +151 -0
  45. package/src/mcp/schemaConverter.ts +100 -0
  46. package/src/mcp/servers/navigation.ts +854 -0
  47. package/src/mcp/toolCatalog.ts +169 -0
  48. package/src/mcp/types.ts +95 -0
  49. package/src/utils/approvalBridge.ts +17 -5
  50. package/src/utils/commands/compact.ts +30 -0
  51. package/src/utils/commands/echo.ts +1 -1
  52. package/src/utils/commands/index.ts +4 -6
  53. package/src/utils/commands/new.ts +15 -0
  54. package/src/utils/commands/types.ts +3 -0
  55. package/src/utils/config.ts +3 -1
  56. package/src/utils/diffRendering.tsx +1 -3
  57. package/src/utils/exploreBridge.ts +10 -0
  58. package/src/utils/markdown.tsx +163 -99
  59. package/src/utils/models.ts +31 -9
  60. package/src/utils/questionBridge.ts +36 -1
  61. package/src/utils/tokenEstimator.ts +32 -0
  62. package/src/utils/toolFormatting.ts +268 -7
  63. package/src/web/app.tsx +72 -72
  64. package/src/web/components/HomePage.tsx +7 -7
  65. package/src/web/components/MessageItem.tsx +22 -22
  66. package/src/web/components/QuestionPanel.tsx +72 -12
  67. package/src/web/components/Sidebar.tsx +0 -2
  68. package/src/web/components/ThinkingIndicator.tsx +1 -0
  69. package/src/web/server.tsx +767 -683
  70. package/src/utils/commands/redo.ts +0 -74
  71. package/src/utils/commands/sessions.ts +0 -129
  72. package/src/utils/commands/undo.ts +0 -75
  73. package/src/utils/undoRedo.ts +0 -429
  74. package/src/utils/undoRedoBridge.ts +0 -45
  75. package/src/utils/undoRedoDb.ts +0 -338
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">Mosaic CLI</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Version 0.71.0</strong>
8
+ <strong>Version 0.73.0</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
@@ -113,9 +113,6 @@ Open http://127.0.0.1:8192 in your browser.
113
113
  |-------------|--------------------------------------|
114
114
  | `/init` | Initialize project context (MOSAIC.md) |
115
115
  | `/help` | Show available commands |
116
- | `/undo` | Undo last file change |
117
- | `/redo` | Redo undone change |
118
- | `/sessions` | Manage conversation sessions |
119
116
  | `/web` | Open web interface |
120
117
  | `/echo` | Echo a message (debug) |
121
118
 
@@ -151,7 +148,6 @@ Mosaic relies on a tool registry that exposes safe, focused capabilities to the
151
148
 
152
149
  **Safety Features:**
153
150
  - Write and edit operations require user approval before execution
154
- - Built-in undo/redo system tracks all file changes (SQLite-backed)
155
151
  - Project context via `MOSAIC.md` helps the agent understand your codebase
156
152
 
157
153
  ## AI Providers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kirosnn/mosaic",
3
- "version": "0.71.0",
3
+ "version": "0.73.0",
4
4
  "description": "The open source coding agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,6 +33,7 @@
33
33
  "@ai-sdk/mistral": "^1.0.0",
34
34
  "@ai-sdk/openai": "^1.0.0",
35
35
  "@ai-sdk/xai": "^1.0.0",
36
+ "@modelcontextprotocol/sdk": "^1.25.3",
36
37
  "@mozilla/readability": "^0.6.0",
37
38
  "@opentui/core": "^0.1.69",
38
39
  "@opentui/react": "^0.1.69",
@@ -41,6 +42,7 @@
41
42
  "better-sqlite3": "^12.6.0",
42
43
  "linkedom": "^0.18.12",
43
44
  "ollama": "^0.5.0",
45
+ "playwright": "^1.45.0",
44
46
  "react": "^19.2.3",
45
47
  "react-dom": "^19.2.3",
46
48
  "react-markdown": "^10.1.0",
@@ -50,4 +52,4 @@
50
52
  "zod": "^3.23.8",
51
53
  "zod-to-json-schema": "^3.25.1"
52
54
  }
53
- }
55
+ }
@@ -1,131 +1,353 @@
1
- import { CoreMessage, CoreTool } from 'ai';
2
- import {
3
- AgentEvent,
4
- AgentMessage,
5
- ProviderConfig,
6
- Provider,
7
- ProviderSendOptions,
8
- } from './types';
9
- import { readConfig } from '../utils/config';
10
- import { DEFAULT_SYSTEM_PROMPT, processSystemPrompt } from './prompts/systemPrompt';
11
- import { getTools } from './tools/definitions';
12
- import { AnthropicProvider } from './provider/anthropic';
13
- import { OpenAIProvider } from './provider/openai';
14
- import { GoogleProvider } from './provider/google';
15
- import { MistralProvider } from './provider/mistral';
16
- import { XaiProvider } from './provider/xai';
17
- import { OllamaProvider, checkAndStartOllama } from './provider/ollama';
18
-
19
- export class Agent {
20
- private messageHistory: CoreMessage[] = [];
21
- private provider: Provider;
22
- private config: ProviderConfig;
23
- private static ollamaChecked = false;
24
-
25
- static async ensureProviderReady(): Promise<{ ready: boolean; started?: boolean; error?: string }> {
26
- const userConfig = readConfig();
27
-
28
- if (userConfig.provider === 'ollama') {
29
- if (Agent.ollamaChecked) {
30
- return { ready: true };
31
- }
32
-
33
- const result = await checkAndStartOllama();
34
- Agent.ollamaChecked = true;
35
-
36
- if (!result.running) {
37
- return { ready: false, error: result.error };
38
- }
39
-
40
- return { ready: true, started: result.started };
41
- }
42
-
43
- return { ready: true };
44
- }
45
-
46
- constructor() {
47
- const userConfig = readConfig();
48
-
49
- if (!userConfig.provider || !userConfig.model) {
50
- throw new Error('No provider or model configured. Please run setup first.');
51
- }
52
-
53
- const rawSystemPrompt = userConfig.systemPrompt || DEFAULT_SYSTEM_PROMPT;
54
- const systemPrompt = processSystemPrompt(rawSystemPrompt, true);
55
- const tools = getTools();
56
-
57
- this.config = {
58
- provider: userConfig.provider,
59
- model: userConfig.model,
60
- apiKey: userConfig.apiKey,
61
- systemPrompt,
62
- tools,
63
- maxSteps: 60,
64
- };
65
-
66
- this.provider = this.createProvider(userConfig.provider);
67
- }
68
-
69
- private createProvider(providerName: string): Provider {
70
- switch (providerName) {
71
- case 'openai':
72
- return new OpenAIProvider();
73
- case 'anthropic':
74
- return new AnthropicProvider();
75
- case 'google':
76
- return new GoogleProvider();
77
- case 'mistral':
78
- return new MistralProvider();
79
- case 'xai':
80
- return new XaiProvider();
81
- case 'ollama':
82
- return new OllamaProvider();
83
- default:
84
- throw new Error(`Unknown provider: ${providerName}`);
85
- }
86
- }
87
-
88
- async *sendMessage(userMessage: string, options?: ProviderSendOptions): AsyncGenerator<AgentEvent> {
89
- this.messageHistory.push({
90
- role: 'user',
91
- content: userMessage,
92
- });
93
-
94
- try {
95
- yield* this.provider.sendMessage(this.messageHistory, this.config, options);
96
- } catch (error) {
97
- yield {
98
- type: 'error',
99
- error: error instanceof Error ? error.message : 'Unknown error occurred',
100
- };
101
- }
102
- }
103
-
104
- async *streamMessages(messages: AgentMessage[], options?: ProviderSendOptions): AsyncGenerator<AgentEvent> {
105
- this.messageHistory = messages.map(msg => ({
106
- role: msg.role,
107
- content: msg.content,
108
- }));
109
-
110
- try {
111
- yield* this.provider.sendMessage(this.messageHistory, this.config, options);
112
- } catch (error) {
113
- yield {
114
- type: 'error',
115
- error: error instanceof Error ? error.message : 'Unknown error occurred',
116
- };
117
- }
118
- }
119
-
120
- getHistory(): CoreMessage[] {
121
- return [...this.messageHistory];
122
- }
123
-
124
- clearHistory(): void {
125
- this.messageHistory = [];
126
- }
127
-
128
- updateConfig(updates: Partial<ProviderConfig>): void {
129
- this.config = { ...this.config, ...updates };
130
- }
131
- }
1
+ import { CoreMessage } from 'ai';
2
+ import {
3
+ AgentEvent,
4
+ AgentMessage,
5
+ ProviderConfig,
6
+ Provider,
7
+ ProviderSendOptions,
8
+ } from './types';
9
+ import { readConfig } from '../utils/config';
10
+ import { DEFAULT_SYSTEM_PROMPT, processSystemPrompt } from './prompts/systemPrompt';
11
+ import { getTools } from './tools/definitions';
12
+ import { AnthropicProvider } from './provider/anthropic';
13
+ import { OpenAIProvider } from './provider/openai';
14
+ import { GoogleProvider } from './provider/google';
15
+ import { MistralProvider } from './provider/mistral';
16
+ import { XaiProvider } from './provider/xai';
17
+ import { OllamaProvider, checkAndStartOllama } from './provider/ollama';
18
+ import { getModelsDevContextLimit } from '../utils/models';
19
+ import { estimateTokensFromText, estimateTokensForContent, getDefaultContextBudget } from '../utils/tokenEstimator';
20
+ import { setExploreContext } from '../utils/exploreBridge';
21
+
22
+ function contentToText(content: CoreMessage['content']): string {
23
+ if (typeof content === 'string') return content;
24
+ if (!content) return '';
25
+
26
+ if (Array.isArray(content)) {
27
+ const text = content
28
+ .map((part: any) => {
29
+ if (part && typeof part.text === 'string') return part.text;
30
+ if (typeof part === 'string') return part;
31
+ return '';
32
+ })
33
+ .filter(Boolean)
34
+ .join('');
35
+
36
+ if (text) return text;
37
+ }
38
+
39
+ try {
40
+ return JSON.stringify(content);
41
+ } catch {
42
+ return String(content);
43
+ }
44
+ }
45
+
46
+ function estimateTokensForMessages(messages: CoreMessage[]): number {
47
+ let total = 0;
48
+ for (const message of messages) {
49
+ total += estimateTokensForContent(contentToText(message.content));
50
+ }
51
+ return total;
52
+ }
53
+
54
+ function normalizeWhitespace(text: string): string {
55
+ return text.replace(/\s+/g, ' ').trim();
56
+ }
57
+
58
+ function truncateText(text: string, maxChars: number): string {
59
+ if (text.length <= maxChars) return text;
60
+ return text.slice(0, Math.max(0, maxChars - 3)) + '...';
61
+ }
62
+
63
+ function summarizeMessage(message: CoreMessage, isLastUser: boolean): string {
64
+ if (message.role === 'tool') {
65
+ const content: any = message.content;
66
+ const part = Array.isArray(content) ? content[0] : undefined;
67
+ const toolName = part?.toolName ?? part?.tool_name ?? 'tool';
68
+ let resultText = '';
69
+ if (part?.result !== undefined) {
70
+ if (typeof part.result === 'string') resultText = part.result;
71
+ else {
72
+ try {
73
+ resultText = JSON.stringify(part.result);
74
+ } catch {
75
+ resultText = String(part.result);
76
+ }
77
+ }
78
+ } else {
79
+ resultText = contentToText(message.content);
80
+ }
81
+ const isError = resultText.toLowerCase().includes('error') || resultText.toLowerCase().includes('failed');
82
+ const status = isError ? 'FAILED' : 'OK';
83
+ const cleaned = normalizeWhitespace(resultText);
84
+ return `[tool:${toolName} ${status}] ${truncateText(cleaned, 120)}`;
85
+ }
86
+
87
+ if (message.role === 'assistant') {
88
+ const text = contentToText(message.content);
89
+ const cleaned = normalizeWhitespace(text);
90
+ const sentenceMatch = cleaned.match(/^[^.!?\n]{10,}[.!?]/);
91
+ const summary = sentenceMatch ? sentenceMatch[0] : cleaned;
92
+ return `assistant: ${truncateText(summary, 200)}`;
93
+ }
94
+
95
+ const cleaned = normalizeWhitespace(contentToText(message.content));
96
+ const limit = isLastUser ? cleaned.length : 400;
97
+ return `user: ${truncateText(cleaned, limit)}`;
98
+ }
99
+
100
+ function buildSummary(messages: CoreMessage[], maxTokens: number): string {
101
+ const maxChars = Math.max(0, maxTokens * 3);
102
+ const header = 'CONVERSATION SUMMARY (auto):';
103
+ let charCount = header.length + 1;
104
+ const lines: string[] = [];
105
+
106
+ let lastUserIndex = -1;
107
+ for (let i = messages.length - 1; i >= 0; i--) {
108
+ if (messages[i]!.role === 'user') { lastUserIndex = i; break; }
109
+ }
110
+
111
+ for (let i = 0; i < messages.length; i++) {
112
+ if (charCount >= maxChars) break;
113
+ const line = `- ${summarizeMessage(messages[i]!, i === lastUserIndex)}`;
114
+ charCount += line.length + 1;
115
+ lines.push(line);
116
+ }
117
+ const body = lines.join('\n');
118
+ const full = `${header}\n${body}`.trim();
119
+ return truncateText(full, maxChars);
120
+ }
121
+
122
+ function compactMessages(
123
+ messages: CoreMessage[],
124
+ systemPrompt: string,
125
+ maxContextTokens?: number,
126
+ provider?: string
127
+ ): CoreMessage[] {
128
+ const budget = maxContextTokens ?? getDefaultContextBudget(provider);
129
+ const systemTokens = estimateTokensFromText(systemPrompt) + 8;
130
+ const messagesTokens = estimateTokensForMessages(messages);
131
+ const total = systemTokens + messagesTokens;
132
+
133
+ if (total <= budget) return messages;
134
+
135
+ const summaryTokens = Math.min(2000, Math.max(400, Math.floor(budget * 0.2)));
136
+ const recentBudget = Math.max(500, budget - summaryTokens);
137
+
138
+ let recentTokens = 0;
139
+ const recent: CoreMessage[] = [];
140
+ for (let i = messages.length - 1; i >= 0; i--) {
141
+ const message = messages[i]!;
142
+ const msgTokens = estimateTokensForContent(contentToText(message.content));
143
+ if (recentTokens + msgTokens > recentBudget && recent.length > 0) break;
144
+ recent.unshift(message);
145
+ recentTokens += msgTokens;
146
+ }
147
+
148
+ const cutoff = messages.length - recent.length;
149
+ const older = cutoff > 0 ? messages.slice(0, cutoff) : [];
150
+
151
+ if (older.length === 0) return recent;
152
+
153
+ const summary = buildSummary(older, summaryTokens);
154
+ const summaryMessage: CoreMessage = { role: 'assistant', content: summary };
155
+
156
+ return [summaryMessage, ...recent];
157
+ }
158
+
159
+ function buildExploreContext(messages: CoreMessage[]): string {
160
+ const parts: string[] = [];
161
+
162
+ const userMessages: string[] = [];
163
+ const recentFiles = new Set<string>();
164
+
165
+ for (let i = messages.length - 1; i >= 0; i--) {
166
+ const msg = messages[i]!;
167
+
168
+ if (msg.role === 'user' && userMessages.length < 2) {
169
+ const text = normalizeWhitespace(contentToText(msg.content));
170
+ if (text) userMessages.unshift(truncateText(text, 300));
171
+ }
172
+
173
+ if (msg.role === 'tool' && recentFiles.size < 15) {
174
+ const content: any = msg.content;
175
+ const part = Array.isArray(content) ? content[0] : undefined;
176
+ const toolName = part?.toolName ?? part?.tool_name;
177
+ if (toolName === 'read' || toolName === 'write' || toolName === 'edit') {
178
+ const args = part?.args ?? part?.input;
179
+ const path = args?.path;
180
+ if (typeof path === 'string') recentFiles.add(path);
181
+ }
182
+ }
183
+ }
184
+
185
+ if (userMessages.length > 0) {
186
+ parts.push(`User intent:\n${userMessages.map(m => `- ${m}`).join('\n')}`);
187
+ }
188
+
189
+ if (recentFiles.size > 0) {
190
+ parts.push(`Files recently accessed:\n${[...recentFiles].map(f => `- ${f}`).join('\n')}`);
191
+ }
192
+
193
+ return parts.join('\n\n');
194
+ }
195
+
196
+ export class Agent {
197
+ private messageHistory: CoreMessage[] = [];
198
+ private provider: Provider;
199
+ private config: ProviderConfig;
200
+ private static ollamaChecked = false;
201
+ private resolvedMaxContextTokens?: number;
202
+
203
+ static async ensureProviderReady(): Promise<{ ready: boolean; started?: boolean; error?: string }> {
204
+ const userConfig = readConfig();
205
+
206
+ if (userConfig.provider === 'ollama') {
207
+ if (Agent.ollamaChecked) {
208
+ return { ready: true };
209
+ }
210
+
211
+ const result = await checkAndStartOllama();
212
+ Agent.ollamaChecked = true;
213
+
214
+ if (!result.running) {
215
+ return { ready: false, error: result.error };
216
+ }
217
+
218
+ return { ready: true, started: result.started };
219
+ }
220
+
221
+ return { ready: true };
222
+ }
223
+
224
+ constructor() {
225
+ const userConfig = readConfig();
226
+
227
+ if (!userConfig.provider || !userConfig.model) {
228
+ throw new Error('No provider or model configured. Please run setup first.');
229
+ }
230
+
231
+ const rawSystemPrompt = userConfig.systemPrompt || DEFAULT_SYSTEM_PROMPT;
232
+
233
+ let mcpToolInfos: Array<{ serverId: string; name: string; description: string; inputSchema: Record<string, unknown>; canonicalId: string; safeId: string }> | undefined;
234
+ try {
235
+ const { getMcpCatalog, isMcpInitialized } = require('../mcp/index');
236
+ if (isMcpInitialized()) {
237
+ mcpToolInfos = getMcpCatalog().getMcpToolInfos();
238
+ }
239
+ } catch {
240
+ // MCP not available
241
+ }
242
+
243
+ const systemPrompt = processSystemPrompt(rawSystemPrompt, true, mcpToolInfos);
244
+ const tools = getTools();
245
+
246
+ this.config = {
247
+ provider: userConfig.provider,
248
+ model: userConfig.model,
249
+ apiKey: userConfig.apiKey,
250
+ systemPrompt,
251
+ tools,
252
+ maxSteps: userConfig.maxSteps ?? 100,
253
+ maxContextTokens: userConfig.maxContextTokens,
254
+ };
255
+
256
+ this.provider = this.createProvider(userConfig.provider);
257
+ }
258
+
259
+ private createProvider(providerName: string): Provider {
260
+ switch (providerName) {
261
+ case 'openai':
262
+ return new OpenAIProvider();
263
+ case 'anthropic':
264
+ return new AnthropicProvider();
265
+ case 'google':
266
+ return new GoogleProvider();
267
+ case 'mistral':
268
+ return new MistralProvider();
269
+ case 'xai':
270
+ return new XaiProvider();
271
+ case 'ollama':
272
+ return new OllamaProvider();
273
+ default:
274
+ throw new Error(`Unknown provider: ${providerName}`);
275
+ }
276
+ }
277
+
278
+ async *sendMessage(userMessage: string, options?: ProviderSendOptions): AsyncGenerator<AgentEvent> {
279
+ this.messageHistory.push({
280
+ role: 'user',
281
+ content: userMessage,
282
+ });
283
+
284
+ try {
285
+ if (this.resolvedMaxContextTokens === undefined) {
286
+ const resolved = await getModelsDevContextLimit(this.config.provider, this.config.model);
287
+ if (typeof resolved === 'number') {
288
+ this.resolvedMaxContextTokens = resolved;
289
+ if (!this.config.maxContextTokens) {
290
+ this.config = { ...this.config, maxContextTokens: resolved };
291
+ }
292
+ }
293
+ }
294
+ const compacted = compactMessages(
295
+ this.messageHistory,
296
+ this.config.systemPrompt,
297
+ this.config.maxContextTokens ?? this.resolvedMaxContextTokens,
298
+ this.config.provider
299
+ );
300
+ setExploreContext(buildExploreContext(this.messageHistory));
301
+ yield* this.provider.sendMessage(compacted, this.config, options);
302
+ } catch (error) {
303
+ yield {
304
+ type: 'error',
305
+ error: error instanceof Error ? error.message : 'Unknown error occurred',
306
+ };
307
+ }
308
+ }
309
+
310
+ async *streamMessages(messages: AgentMessage[], options?: ProviderSendOptions): AsyncGenerator<AgentEvent> {
311
+ this.messageHistory = messages.map(msg => ({
312
+ role: msg.role,
313
+ content: msg.content,
314
+ })) as CoreMessage[];
315
+
316
+ try {
317
+ if (this.resolvedMaxContextTokens === undefined) {
318
+ const resolved = await getModelsDevContextLimit(this.config.provider, this.config.model);
319
+ if (typeof resolved === 'number') {
320
+ this.resolvedMaxContextTokens = resolved;
321
+ if (!this.config.maxContextTokens) {
322
+ this.config = { ...this.config, maxContextTokens: resolved };
323
+ }
324
+ }
325
+ }
326
+ const compacted = compactMessages(
327
+ this.messageHistory,
328
+ this.config.systemPrompt,
329
+ this.config.maxContextTokens ?? this.resolvedMaxContextTokens,
330
+ this.config.provider
331
+ );
332
+ setExploreContext(buildExploreContext(this.messageHistory));
333
+ yield* this.provider.sendMessage(compacted, this.config, options);
334
+ } catch (error) {
335
+ yield {
336
+ type: 'error',
337
+ error: error instanceof Error ? error.message : 'Unknown error occurred',
338
+ };
339
+ }
340
+ }
341
+
342
+ getHistory(): CoreMessage[] {
343
+ return [...this.messageHistory];
344
+ }
345
+
346
+ clearHistory(): void {
347
+ this.messageHistory = [];
348
+ }
349
+
350
+ updateConfig(updates: Partial<ProviderConfig>): void {
351
+ this.config = { ...this.config, ...updates };
352
+ }
353
+ }
@@ -73,9 +73,9 @@ export class AgentContextManager {
73
73
  return this.currentStep;
74
74
  }
75
75
 
76
- getMaxSteps(): number {
77
- return this.config.maxSteps || 10;
78
- }
76
+ getMaxSteps(): number {
77
+ return this.config.maxSteps || 100;
78
+ }
79
79
 
80
80
  canContinue(): boolean {
81
81
  return this.currentStep < this.getMaxSteps();
@@ -93,4 +93,4 @@ export class AgentContextManager {
93
93
  getMessageCount(): number {
94
94
  return this.messages.length;
95
95
  }
96
- }
96
+ }
@@ -5,7 +5,7 @@ import { getToolsPrompt } from './toolsPrompt';
5
5
 
6
6
  export const DEFAULT_SYSTEM_PROMPT = `You are Mosaic, an AI coding agent operating in the user's terminal.
7
7
  You assist with software engineering tasks: coding, debugging, refactoring, testing, and documentation.
8
- Version : 0.71.0 *(Beta)*
8
+ Version : 0.73.0 *(Beta)*
9
9
 
10
10
  # Environment
11
11
 
@@ -109,10 +109,18 @@ USE glob/grep for TARGETED searches:
109
109
 
110
110
  CRITICAL: NEVER modify code you haven't read. Always use read before edit/write.
111
111
 
112
- ## 2. PLAN (for multi-step tasks)
112
+ ## 2. PLAN (Default for non-trivial tasks)
113
113
 
114
- Use the plan tool to outline steps and track progress.
115
- Always update the plan after each step.
114
+ Use the plan tool for any task that isn't a single obvious step.
115
+ Default to planning when there are 2+ actions, file changes, or when success criteria are unclear.
116
+ Keep plans short (3-6 steps), and make steps outcome-focused.
117
+ Always update the plan after each step:
118
+ - Exactly one step can be "in_progress" at a time
119
+ - Mark a step "completed" before starting the next
120
+ - Keep remaining steps "pending" until started
121
+ Never mark multiple future steps as completed in a single update. Progress must be shown step-by-step as work happens.
122
+ Skip the plan tool only for truly trivial, single-action tasks.
123
+ Never output a plan as plain text, JSON, or tags. The only valid way to create or update a plan is the plan tool call.
116
124
 
117
125
  ## 3. EXECUTE
118
126
 
@@ -153,6 +161,7 @@ When NOT to ask:
153
161
  # Error Handling
154
162
 
155
163
  - If a tool fails, analyze the error and retry with adjusted parameters
164
+ - If a rate limit error occurs, wait with backoff before retrying and avoid immediate reattempts
156
165
  - Announce the error to the user and explain your retry strategy
157
166
  - Try 2-3 different approaches before giving up
158
167
  - Only ask the user for help after multiple failed attempts
@@ -208,7 +217,7 @@ When you discover useful commands or preferences, offer to save them to MOSAIC.m
208
217
  All requests refer to the current workspace ({{WORKSPACE}}), never to Mosaic itself.
209
218
  `;
210
219
 
211
- export function processSystemPrompt(prompt: string, includeTools: boolean = true): string {
220
+ export function processSystemPrompt(prompt: string, includeTools: boolean = true, mcpToolInfos?: Array<{ serverId: string; name: string; description: string; inputSchema: Record<string, unknown>; canonicalId: string; safeId: string }>): string {
212
221
  const now = new Date();
213
222
  const workspace = process.cwd();
214
223
  const os = platform();
@@ -259,7 +268,7 @@ ${mosaicContent}`;
259
268
  }
260
269
 
261
270
  if (includeTools) {
262
- const toolsPrompt = getToolsPrompt();
271
+ const toolsPrompt = getToolsPrompt(mcpToolInfos);
263
272
  const processedToolsPrompt = toolsPrompt.replace(new RegExp('{{WORKSPACE}}', 'g'), workspace);
264
273
  processed = `${processed}\n\n${processedToolsPrompt}`;
265
274
  }