@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.
- package/README.md +1 -5
- package/package.json +4 -2
- package/src/agent/Agent.ts +353 -131
- package/src/agent/context.ts +4 -4
- package/src/agent/prompts/systemPrompt.ts +15 -6
- package/src/agent/prompts/toolsPrompt.ts +75 -10
- package/src/agent/provider/anthropic.ts +100 -100
- package/src/agent/provider/google.ts +102 -102
- package/src/agent/provider/mistral.ts +95 -95
- package/src/agent/provider/ollama.ts +77 -60
- package/src/agent/provider/openai.ts +42 -38
- package/src/agent/provider/rateLimit.ts +178 -0
- package/src/agent/provider/xai.ts +99 -99
- package/src/agent/tools/definitions.ts +19 -9
- package/src/agent/tools/executor.ts +95 -85
- package/src/agent/tools/exploreExecutor.ts +8 -10
- package/src/agent/tools/grep.ts +30 -29
- package/src/agent/tools/question.ts +7 -1
- package/src/agent/types.ts +9 -8
- package/src/components/App.tsx +45 -45
- package/src/components/CustomInput.tsx +214 -36
- package/src/components/Main.tsx +1146 -954
- package/src/components/Setup.tsx +1 -1
- package/src/components/Welcome.tsx +1 -1
- package/src/components/main/ApprovalPanel.tsx +4 -3
- package/src/components/main/ChatPage.tsx +858 -675
- package/src/components/main/HomePage.tsx +53 -38
- package/src/components/main/QuestionPanel.tsx +52 -7
- package/src/components/main/ThinkingIndicator.tsx +2 -1
- package/src/index.tsx +50 -20
- package/src/mcp/approvalPolicy.ts +148 -0
- package/src/mcp/cli/add.ts +185 -0
- package/src/mcp/cli/doctor.ts +77 -0
- package/src/mcp/cli/index.ts +85 -0
- package/src/mcp/cli/list.ts +50 -0
- package/src/mcp/cli/logs.ts +24 -0
- package/src/mcp/cli/manage.ts +99 -0
- package/src/mcp/cli/show.ts +53 -0
- package/src/mcp/cli/tools.ts +77 -0
- package/src/mcp/config.ts +223 -0
- package/src/mcp/index.ts +80 -0
- package/src/mcp/processManager.ts +299 -0
- package/src/mcp/rateLimiter.ts +50 -0
- package/src/mcp/registry.ts +151 -0
- package/src/mcp/schemaConverter.ts +100 -0
- package/src/mcp/servers/navigation.ts +854 -0
- package/src/mcp/toolCatalog.ts +169 -0
- package/src/mcp/types.ts +95 -0
- package/src/utils/approvalBridge.ts +17 -5
- package/src/utils/commands/compact.ts +30 -0
- package/src/utils/commands/echo.ts +1 -1
- package/src/utils/commands/index.ts +4 -6
- package/src/utils/commands/new.ts +15 -0
- package/src/utils/commands/types.ts +3 -0
- package/src/utils/config.ts +3 -1
- package/src/utils/diffRendering.tsx +1 -3
- package/src/utils/exploreBridge.ts +10 -0
- package/src/utils/markdown.tsx +163 -99
- package/src/utils/models.ts +31 -9
- package/src/utils/questionBridge.ts +36 -1
- package/src/utils/tokenEstimator.ts +32 -0
- package/src/utils/toolFormatting.ts +268 -7
- package/src/web/app.tsx +72 -72
- package/src/web/components/HomePage.tsx +7 -7
- package/src/web/components/MessageItem.tsx +22 -22
- package/src/web/components/QuestionPanel.tsx +72 -12
- package/src/web/components/Sidebar.tsx +0 -2
- package/src/web/components/ThinkingIndicator.tsx +1 -0
- package/src/web/server.tsx +767 -683
- package/src/utils/commands/redo.ts +0 -74
- package/src/utils/commands/sessions.ts +0 -129
- package/src/utils/commands/undo.ts +0 -75
- package/src/utils/undoRedo.ts +0 -429
- package/src/utils/undoRedoBridge.ts +0 -45
- 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.
|
|
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.
|
|
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
|
+
}
|
package/src/agent/Agent.ts
CHANGED
|
@@ -1,131 +1,353 @@
|
|
|
1
|
-
import { CoreMessage
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
}
|
package/src/agent/context.ts
CHANGED
|
@@ -73,9 +73,9 @@ export class AgentContextManager {
|
|
|
73
73
|
return this.currentStep;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
getMaxSteps(): number {
|
|
77
|
-
return this.config.maxSteps ||
|
|
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.
|
|
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
|
|
112
|
+
## 2. PLAN (Default for non-trivial tasks)
|
|
113
113
|
|
|
114
|
-
Use the plan tool
|
|
115
|
-
|
|
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
|
}
|