@kirosnn/mosaic 0.0.91 → 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/LICENSE +1 -1
- package/README.md +2 -6
- package/package.json +55 -48
- package/src/agent/Agent.ts +353 -131
- package/src/agent/context.ts +4 -4
- package/src/agent/prompts/systemPrompt.ts +209 -70
- package/src/agent/prompts/toolsPrompt.ts +285 -138
- package/src/agent/provider/anthropic.ts +109 -105
- package/src/agent/provider/google.ts +111 -107
- package/src/agent/provider/mistral.ts +95 -95
- package/src/agent/provider/ollama.ts +73 -17
- package/src/agent/provider/openai.ts +146 -102
- package/src/agent/provider/rateLimit.ts +178 -0
- package/src/agent/provider/reasoning.ts +29 -0
- package/src/agent/provider/xai.ts +108 -104
- package/src/agent/tools/definitions.ts +15 -1
- package/src/agent/tools/executor.ts +717 -98
- package/src/agent/tools/exploreExecutor.ts +20 -22
- package/src/agent/tools/fetch.ts +58 -0
- package/src/agent/tools/glob.ts +20 -4
- package/src/agent/tools/grep.ts +64 -9
- package/src/agent/tools/plan.ts +27 -0
- package/src/agent/tools/question.ts +7 -1
- package/src/agent/tools/read.ts +2 -0
- package/src/agent/types.ts +15 -14
- package/src/components/App.tsx +50 -8
- package/src/components/CustomInput.tsx +461 -77
- package/src/components/Main.tsx +1459 -1112
- package/src/components/Setup.tsx +1 -1
- package/src/components/ShortcutsModal.tsx +11 -8
- package/src/components/Welcome.tsx +1 -1
- package/src/components/main/ApprovalPanel.tsx +4 -3
- package/src/components/main/ChatPage.tsx +858 -516
- package/src/components/main/HomePage.tsx +58 -39
- package/src/components/main/QuestionPanel.tsx +52 -7
- package/src/components/main/ThinkingIndicator.tsx +13 -2
- package/src/components/main/types.ts +11 -10
- package/src/index.tsx +53 -25
- 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 +45 -12
- package/src/utils/approvalModeBridge.ts +17 -0
- package/src/utils/commands/approvals.ts +48 -0
- package/src/utils/commands/compact.ts +30 -0
- package/src/utils/commands/echo.ts +1 -1
- package/src/utils/commands/image.ts +109 -0
- package/src/utils/commands/index.ts +9 -7
- 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 +13 -16
- package/src/utils/exploreBridge.ts +10 -0
- package/src/utils/history.ts +82 -40
- package/src/utils/imageBridge.ts +28 -0
- package/src/utils/images.ts +31 -0
- package/src/utils/markdown.tsx +163 -99
- package/src/utils/models.ts +31 -16
- package/src/utils/notificationBridge.ts +23 -0
- package/src/utils/questionBridge.ts +36 -1
- package/src/utils/tokenEstimator.ts +32 -0
- package/src/utils/toolFormatting.ts +428 -48
- package/src/web/app.tsx +65 -5
- package/src/web/assets/css/ChatPage.css +102 -30
- package/src/web/assets/css/MessageItem.css +26 -29
- package/src/web/assets/css/ThinkingIndicator.css +44 -6
- package/src/web/assets/css/ToolMessage.css +36 -14
- package/src/web/components/ChatPage.tsx +228 -105
- package/src/web/components/HomePage.tsx +3 -3
- package/src/web/components/MessageItem.tsx +80 -81
- package/src/web/components/QuestionPanel.tsx +72 -12
- package/src/web/components/Setup.tsx +1 -1
- package/src/web/components/Sidebar.tsx +1 -3
- package/src/web/components/ThinkingIndicator.tsx +41 -21
- package/src/web/router.ts +1 -1
- package/src/web/server.tsx +894 -662
- package/src/web/storage.ts +23 -1
- package/src/web/types.ts +7 -6
- 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
|
@@ -7,7 +7,7 @@ import { createXai } from '@ai-sdk/xai';
|
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { readConfig } from '../../utils/config';
|
|
9
9
|
import { executeTool } from './executor';
|
|
10
|
-
import { getExploreAbortSignal, isExploreAborted, notifyExploreTool } from '../../utils/exploreBridge';
|
|
10
|
+
import { getExploreAbortSignal, isExploreAborted, notifyExploreTool, getExploreContext } from '../../utils/exploreBridge';
|
|
11
11
|
|
|
12
12
|
interface ExploreLog {
|
|
13
13
|
tool: string;
|
|
@@ -36,7 +36,7 @@ IMPORTANT RULES:
|
|
|
36
36
|
5. Summarize findings clearly and include relevant file paths and code snippets
|
|
37
37
|
6. You MUST call the "done" tool when finished - this is the only way to complete the exploration`;
|
|
38
38
|
|
|
39
|
-
const MAX_STEPS =
|
|
39
|
+
const MAX_STEPS = 100;
|
|
40
40
|
|
|
41
41
|
interface ExploreResult {
|
|
42
42
|
success: boolean;
|
|
@@ -78,13 +78,6 @@ function createModelProvider(config: { provider: string; model: string; apiKey?:
|
|
|
78
78
|
|
|
79
79
|
let exploreDoneResult: string | null = null;
|
|
80
80
|
|
|
81
|
-
function getResultPreview(result: string | undefined): string {
|
|
82
|
-
if (!result) return '';
|
|
83
|
-
const lines = result.split('\n');
|
|
84
|
-
if (lines.length <= 3) return result.substring(0, 200);
|
|
85
|
-
return lines.slice(0, 3).join('\n').substring(0, 200) + '...';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
81
|
function createExploreTools() {
|
|
89
82
|
return {
|
|
90
83
|
read: createTool({
|
|
@@ -104,10 +97,10 @@ function createExploreTools() {
|
|
|
104
97
|
},
|
|
105
98
|
}),
|
|
106
99
|
glob: createTool({
|
|
107
|
-
description: 'Find files matching a glob pattern',
|
|
100
|
+
description: 'Find files matching a glob pattern. Do NOT use this to list directory contents (use "list" instead).',
|
|
108
101
|
parameters: z.object({
|
|
109
102
|
pattern: z.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.tsx")'),
|
|
110
|
-
path: z.string().
|
|
103
|
+
path: z.string().describe('Directory to search in (use "." for workspace root)'),
|
|
111
104
|
}),
|
|
112
105
|
execute: async (args) => {
|
|
113
106
|
if (isExploreAborted()) return { error: 'Exploration aborted' };
|
|
@@ -127,17 +120,17 @@ function createExploreTools() {
|
|
|
127
120
|
},
|
|
128
121
|
}),
|
|
129
122
|
grep: createTool({
|
|
130
|
-
description: 'Search for text content within files',
|
|
123
|
+
description: 'Search for text content within files using regular expressions',
|
|
131
124
|
parameters: z.object({
|
|
132
125
|
pattern: z.string().describe('Glob pattern to match files'),
|
|
133
|
-
query: z.string().describe('
|
|
134
|
-
path: z.string().
|
|
135
|
-
case_sensitive: z.boolean().
|
|
136
|
-
max_results: z.number().
|
|
126
|
+
query: z.string().describe('Regular expression pattern to search for'),
|
|
127
|
+
path: z.string().describe('Directory to search in (use "." for workspace root)'),
|
|
128
|
+
case_sensitive: z.boolean().describe('Case-sensitive search (pass false for default)'),
|
|
129
|
+
max_results: z.number().describe('Maximum results (pass 50 for default)'),
|
|
137
130
|
}),
|
|
138
131
|
execute: async (args) => {
|
|
139
132
|
if (isExploreAborted()) return { error: 'Exploration aborted' };
|
|
140
|
-
const result = await executeTool('grep', args);
|
|
133
|
+
const result = await executeTool('grep', { ...args, regex: true });
|
|
141
134
|
const resultLen = result.result?.length || 0;
|
|
142
135
|
let preview = result.error || 'error';
|
|
143
136
|
if (result.success && result.result) {
|
|
@@ -156,9 +149,9 @@ function createExploreTools() {
|
|
|
156
149
|
description: 'List files and directories',
|
|
157
150
|
parameters: z.object({
|
|
158
151
|
path: z.string().describe('Path to list'),
|
|
159
|
-
recursive: z.boolean().
|
|
160
|
-
filter: z.string().
|
|
161
|
-
include_hidden: z.boolean().
|
|
152
|
+
recursive: z.boolean().describe('List recursively (pass false for default)'),
|
|
153
|
+
filter: z.string().describe('Filter pattern (pass empty string for no filter)'),
|
|
154
|
+
include_hidden: z.boolean().describe('Include hidden files (pass false for default)'),
|
|
162
155
|
}),
|
|
163
156
|
execute: async (args) => {
|
|
164
157
|
if (isExploreAborted()) return { error: 'Exploration aborted' };
|
|
@@ -196,7 +189,7 @@ function formatExploreLogs(): string {
|
|
|
196
189
|
const lines = ['Tools used:'];
|
|
197
190
|
for (const log of exploreLogs) {
|
|
198
191
|
const argStr = log.args.path || log.args.pattern || log.args.query || '';
|
|
199
|
-
const status = log.success ? '
|
|
192
|
+
const status = log.success ? '→' : '-';
|
|
200
193
|
lines.push(` ${status} ${log.tool}(${argStr}) -> ${log.resultPreview || 'ok'}`);
|
|
201
194
|
}
|
|
202
195
|
return lines.join('\n');
|
|
@@ -240,6 +233,11 @@ export async function executeExploreTool(purpose: string): Promise<ExploreResult
|
|
|
240
233
|
|
|
241
234
|
const tools = createExploreTools();
|
|
242
235
|
|
|
236
|
+
const parentContext = getExploreContext();
|
|
237
|
+
const systemPrompt = parentContext
|
|
238
|
+
? `${EXPLORE_SYSTEM_PROMPT}\n\nCONTEXT FROM PARENT CONVERSATION:\n${parentContext}`
|
|
239
|
+
: EXPLORE_SYSTEM_PROMPT;
|
|
240
|
+
|
|
243
241
|
const result = streamText({
|
|
244
242
|
model,
|
|
245
243
|
messages: [
|
|
@@ -248,7 +246,7 @@ export async function executeExploreTool(purpose: string): Promise<ExploreResult
|
|
|
248
246
|
content: `Explore the codebase to: ${purpose}`,
|
|
249
247
|
},
|
|
250
248
|
],
|
|
251
|
-
system:
|
|
249
|
+
system: systemPrompt,
|
|
252
250
|
tools,
|
|
253
251
|
maxSteps: MAX_STEPS,
|
|
254
252
|
abortSignal,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { tool, type CoreTool } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { executeTool } from './executor';
|
|
4
|
+
|
|
5
|
+
export const fetch: CoreTool = tool({
|
|
6
|
+
description: `Fetches a URL from the internet and extracts its contents as markdown.
|
|
7
|
+
|
|
8
|
+
This tool allows you to retrieve web content and process it for analysis. HTML pages are automatically converted to clean markdown format for easier reading.
|
|
9
|
+
|
|
10
|
+
Features:
|
|
11
|
+
- Automatic HTML to Markdown conversion with Readability
|
|
12
|
+
- Pagination support for large pages (use start_index to continue reading)
|
|
13
|
+
- Raw HTML retrieval option
|
|
14
|
+
- Link and image URL resolution to absolute paths
|
|
15
|
+
- Configurable content length limits
|
|
16
|
+
|
|
17
|
+
Use cases:
|
|
18
|
+
- Reading documentation and articles
|
|
19
|
+
- Fetching API responses (JSON, XML, etc.)
|
|
20
|
+
- Researching information from the web
|
|
21
|
+
- Analyzing web page content`,
|
|
22
|
+
parameters: z.object({
|
|
23
|
+
url: z.string().describe('The URL to fetch (must be a valid HTTP/HTTPS URL)'),
|
|
24
|
+
max_length: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.positive()
|
|
28
|
+
.max(100000)
|
|
29
|
+
.nullable()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('Maximum number of characters to return (default: 10000, max: 100000)'),
|
|
32
|
+
start_index: z
|
|
33
|
+
.number()
|
|
34
|
+
.int()
|
|
35
|
+
.nonnegative()
|
|
36
|
+
.nullable()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Character index to start reading from. Use this to paginate through large pages when content is truncated.'),
|
|
39
|
+
raw: z
|
|
40
|
+
.boolean()
|
|
41
|
+
.nullable()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('If true, return raw HTML instead of converting to markdown'),
|
|
44
|
+
timeout: z
|
|
45
|
+
.number()
|
|
46
|
+
.int()
|
|
47
|
+
.positive()
|
|
48
|
+
.max(60000)
|
|
49
|
+
.nullable()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe('Request timeout in milliseconds (default: 30000, max: 60000)'),
|
|
52
|
+
}),
|
|
53
|
+
execute: async (args) => {
|
|
54
|
+
const result = await executeTool('fetch', args);
|
|
55
|
+
if (!result.success) return { error: result.error || 'Unknown error occurred' };
|
|
56
|
+
return result.result;
|
|
57
|
+
},
|
|
58
|
+
});
|
package/src/agent/tools/glob.ts
CHANGED
|
@@ -3,13 +3,29 @@ import { z } from 'zod';
|
|
|
3
3
|
import { executeTool } from './executor';
|
|
4
4
|
|
|
5
5
|
export const glob: CoreTool = tool({
|
|
6
|
-
description:
|
|
6
|
+
description: `Find files matching a glob pattern. Fast pattern-based file discovery.
|
|
7
|
+
|
|
8
|
+
IMPORTANT: Use "**/" prefix to search recursively in all subdirectories.
|
|
9
|
+
- "*.ts" only matches files in the current directory
|
|
10
|
+
- "**/*.ts" matches files in ALL subdirectories (usually what you want)
|
|
11
|
+
|
|
12
|
+
Note: Do not use this to simply list files in a directory; use the 'list' tool for that. This is for finding specific files by pattern.
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
- glob(pattern="**/*.ts") - All TypeScript files
|
|
16
|
+
- glob(pattern="**/*.tsx") - All React components
|
|
17
|
+
- glob(pattern="**/package.json") - All package.json files
|
|
18
|
+
- glob(pattern="src/**/*.js") - All JS files in src/`,
|
|
7
19
|
parameters: z.object({
|
|
8
|
-
pattern: z.string().describe('Glob pattern
|
|
9
|
-
path: z.string().
|
|
20
|
+
pattern: z.string().describe('Glob pattern (use **/ for recursive search, e.g., "**/*.ts")'),
|
|
21
|
+
path: z.string().optional().describe('Directory to search in (defaults to workspace root)'),
|
|
10
22
|
}),
|
|
11
23
|
execute: async (args) => {
|
|
12
|
-
const
|
|
24
|
+
const cleanArgs = {
|
|
25
|
+
pattern: args.pattern,
|
|
26
|
+
path: args.path && args.path !== 'null' ? args.path : undefined,
|
|
27
|
+
};
|
|
28
|
+
const result = await executeTool('glob', cleanArgs);
|
|
13
29
|
if (!result.success) return { error: result.error || 'Unknown error occurred' };
|
|
14
30
|
return result.result;
|
|
15
31
|
},
|
package/src/agent/tools/grep.ts
CHANGED
|
@@ -2,18 +2,73 @@ import { tool, type CoreTool } from 'ai';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { executeTool } from './executor';
|
|
4
4
|
|
|
5
|
+
const FILE_TYPE_EXTENSIONS: Record<string, string[]> = {
|
|
6
|
+
ts: ['.ts', '.tsx', '.mts', '.cts'],
|
|
7
|
+
js: ['.js', '.jsx', '.mjs', '.cjs'],
|
|
8
|
+
py: ['.py', '.pyw', '.pyi'],
|
|
9
|
+
java: ['.java'],
|
|
10
|
+
go: ['.go'],
|
|
11
|
+
rust: ['.rs'],
|
|
12
|
+
c: ['.c', '.h'],
|
|
13
|
+
cpp: ['.cpp', '.cc', '.cxx', '.hpp', '.hh', '.hxx'],
|
|
14
|
+
cs: ['.cs'],
|
|
15
|
+
rb: ['.rb', '.rake', '.gemspec'],
|
|
16
|
+
php: ['.php', '.phtml'],
|
|
17
|
+
swift: ['.swift'],
|
|
18
|
+
kt: ['.kt', '.kts'],
|
|
19
|
+
scala: ['.scala'],
|
|
20
|
+
html: ['.html', '.htm', '.xhtml'],
|
|
21
|
+
css: ['.css', '.scss', '.sass', '.less'],
|
|
22
|
+
json: ['.json', '.jsonc'],
|
|
23
|
+
yaml: ['.yaml', '.yml'],
|
|
24
|
+
md: ['.md', '.markdown'],
|
|
25
|
+
txt: ['.txt'],
|
|
26
|
+
sh: ['.sh', '.bash', '.zsh'],
|
|
27
|
+
sql: ['.sql'],
|
|
28
|
+
vue: ['.vue'],
|
|
29
|
+
svelte: ['.svelte'],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { FILE_TYPE_EXTENSIONS };
|
|
33
|
+
|
|
5
34
|
export const grep: CoreTool = tool({
|
|
6
|
-
description:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
35
|
+
description: `Search for text within files using regular expressions.
|
|
36
|
+
|
|
37
|
+
RECOMMENDED: Use file_type for best results (automatically searches all subdirectories).
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
- grep(query="interface User", file_type="ts") - Search in TypeScript files
|
|
41
|
+
- grep(query="export function", file_type="tsx") - Search in React files
|
|
42
|
+
- grep(query="TODO") - Search in all files
|
|
43
|
+
- grep(query="class.*Component", file_type="ts") - Reuse regex search
|
|
44
|
+
- grep(query="handleClick", output_mode="files") - Just list matching files`,
|
|
45
|
+
parameters: z.object({
|
|
46
|
+
query: z.string().describe('Regular expression pattern to search for'),
|
|
47
|
+
file_type: z.string().optional().describe('File type or extension (e.g. ts, tsx, js, txt, .env). Unknown types are treated as extensions.'),
|
|
48
|
+
pattern: z.string().optional().describe('Glob pattern for files (e.g., "**/*.config.ts"). Usually file_type is easier.'),
|
|
49
|
+
path: z.string().optional().describe('Directory to search (defaults to workspace root)'),
|
|
50
|
+
case_sensitive: z.boolean().optional().describe('Case-sensitive (default: false)'),
|
|
51
|
+
whole_word: z.boolean().optional().describe('Match whole words only (default: false)'),
|
|
52
|
+
context: z.number().optional().describe('Lines of context around matches (default: 0)'),
|
|
53
|
+
max_results: z.number().optional().describe('Max results (default: 500)'),
|
|
54
|
+
output_mode: z.enum(['matches', 'files', 'count']).optional().describe('"matches" (default), "files", or "count"'),
|
|
55
|
+
exclude_pattern: z.string().optional().describe('Glob pattern to exclude'),
|
|
13
56
|
}),
|
|
14
57
|
execute: async (args) => {
|
|
15
|
-
const
|
|
58
|
+
const cleanArgs: Record<string, unknown> = { query: args.query, regex: true };
|
|
59
|
+
|
|
60
|
+
if (args.file_type && args.file_type !== 'null') cleanArgs.file_type = args.file_type;
|
|
61
|
+
if (args.pattern && args.pattern !== 'null') cleanArgs.pattern = args.pattern;
|
|
62
|
+
if (args.path && args.path !== 'null') cleanArgs.path = args.path;
|
|
63
|
+
if (args.case_sensitive !== undefined && args.case_sensitive !== null) cleanArgs.case_sensitive = args.case_sensitive;
|
|
64
|
+
if (args.whole_word !== undefined && args.whole_word !== null) cleanArgs.whole_word = args.whole_word;
|
|
65
|
+
if (args.context !== undefined && args.context !== null) cleanArgs.context = args.context;
|
|
66
|
+
if (args.max_results !== undefined && args.max_results !== null) cleanArgs.max_results = args.max_results;
|
|
67
|
+
if (args.output_mode && (args.output_mode as string) !== 'null') cleanArgs.output_mode = args.output_mode;
|
|
68
|
+
if (args.exclude_pattern && args.exclude_pattern !== 'null') cleanArgs.exclude_pattern = args.exclude_pattern;
|
|
69
|
+
|
|
70
|
+
const result = await executeTool('grep', cleanArgs);
|
|
16
71
|
if (!result.success) return { error: result.error || 'Unknown error occurred' };
|
|
17
72
|
return result.result;
|
|
18
73
|
},
|
|
19
|
-
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { tool, type CoreTool } from 'ai';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const PlanStepSchema = z.object({
|
|
5
|
+
step: z.string().describe('A short, specific action item.'),
|
|
6
|
+
status: z.enum(['pending', 'in_progress', 'completed']).describe('Current status of the step.'),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const plan: CoreTool = tool({
|
|
10
|
+
description: 'Create or update a task plan for longer work, and keep it up to date as you progress. Update the plan after each step.',
|
|
11
|
+
parameters: z.object({
|
|
12
|
+
explanation: z.string().optional().describe('Optional context about the plan or changes.'),
|
|
13
|
+
plan: z.array(PlanStepSchema).min(1).describe('Ordered list of steps with statuses.'),
|
|
14
|
+
}),
|
|
15
|
+
execute: async (args) => {
|
|
16
|
+
const rawPlan = Array.isArray(args.plan) ? args.plan : [];
|
|
17
|
+
const normalizedPlan = rawPlan
|
|
18
|
+
.map((item) => ({
|
|
19
|
+
step: typeof item.step === 'string' ? item.step : '',
|
|
20
|
+
status: item.status === 'completed' || item.status === 'in_progress' ? item.status : 'pending',
|
|
21
|
+
}))
|
|
22
|
+
.filter((item) => item.step.trim().length > 0);
|
|
23
|
+
|
|
24
|
+
const explanation = typeof args.explanation === 'string' ? args.explanation : undefined;
|
|
25
|
+
return { explanation, plan: normalizedPlan };
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -10,11 +10,17 @@ export const question: CoreTool = tool({
|
|
|
10
10
|
z.object({
|
|
11
11
|
label: z.string().describe('The option label shown to the user.'),
|
|
12
12
|
value: z.string().nullable().optional().describe('Optional value returned for the selected option. Use null if not needed.'),
|
|
13
|
+
group: z.string().optional().describe('Optional group name. Consecutive options with the same group are displayed under a shared header.'),
|
|
13
14
|
})
|
|
14
15
|
).describe('List of options the user can pick from. A text input field is automatically displayed below the options where the user can type a custom response instead.'),
|
|
16
|
+
timeout: z.number().optional().describe('Optional timeout in seconds. The question is automatically rejected when time runs out.'),
|
|
17
|
+
validation: z.object({
|
|
18
|
+
pattern: z.string().describe('Regex pattern the custom text must match.'),
|
|
19
|
+
message: z.string().optional().describe('Error message shown when validation fails.'),
|
|
20
|
+
}).optional().describe('Optional validation for the custom text input.'),
|
|
15
21
|
}),
|
|
16
22
|
execute: async (args) => {
|
|
17
|
-
const answer = await askQuestion(args.prompt, args.options);
|
|
23
|
+
const answer = await askQuestion(args.prompt, args.options, args.timeout, args.validation);
|
|
18
24
|
return answer;
|
|
19
25
|
},
|
|
20
26
|
});
|
package/src/agent/tools/read.ts
CHANGED
|
@@ -6,6 +6,8 @@ export const read: CoreTool = tool({
|
|
|
6
6
|
description: 'Read the contents of a file from the workspace',
|
|
7
7
|
parameters: z.object({
|
|
8
8
|
path: z.string().describe('The path to the file relative to the workspace root'),
|
|
9
|
+
start_line: z.number().optional().describe('The line number to start reading from (1-based)'),
|
|
10
|
+
end_line: z.number().optional().describe('The line number to end reading at (1-based)'),
|
|
9
11
|
}),
|
|
10
12
|
execute: async (args) => {
|
|
11
13
|
const result = await executeTool('read', args);
|
package/src/agent/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CoreMessage, CoreTool } from 'ai';
|
|
1
|
+
import { CoreMessage, CoreTool, UserContent } from 'ai';
|
|
2
2
|
|
|
3
3
|
export type AgentEventType =
|
|
4
4
|
| 'text-delta'
|
|
@@ -117,14 +117,15 @@ export type AgentEvent =
|
|
|
117
117
|
| FinishEvent
|
|
118
118
|
| ErrorEvent;
|
|
119
119
|
|
|
120
|
-
export interface ProviderConfig {
|
|
121
|
-
provider: string;
|
|
122
|
-
model: string;
|
|
123
|
-
apiKey?: string;
|
|
124
|
-
systemPrompt: string;
|
|
125
|
-
tools?: Record<string, CoreTool>;
|
|
126
|
-
maxSteps?: number;
|
|
127
|
-
|
|
120
|
+
export interface ProviderConfig {
|
|
121
|
+
provider: string;
|
|
122
|
+
model: string;
|
|
123
|
+
apiKey?: string;
|
|
124
|
+
systemPrompt: string;
|
|
125
|
+
tools?: Record<string, CoreTool>;
|
|
126
|
+
maxSteps?: number;
|
|
127
|
+
maxContextTokens?: number;
|
|
128
|
+
}
|
|
128
129
|
|
|
129
130
|
export interface AgentConfig {
|
|
130
131
|
maxSteps?: number;
|
|
@@ -137,10 +138,10 @@ export interface AgentContext {
|
|
|
137
138
|
config: AgentConfig;
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
export interface AgentMessage {
|
|
141
|
-
role: 'user' | 'assistant';
|
|
142
|
-
content: string;
|
|
143
|
-
}
|
|
141
|
+
export interface AgentMessage {
|
|
142
|
+
role: 'user' | 'assistant';
|
|
143
|
+
content: string | UserContent;
|
|
144
|
+
}
|
|
144
145
|
|
|
145
146
|
export interface ProviderSendOptions {
|
|
146
147
|
abortSignal?: AbortSignal;
|
|
@@ -152,4 +153,4 @@ export interface Provider {
|
|
|
152
153
|
config: ProviderConfig,
|
|
153
154
|
options?: ProviderSendOptions
|
|
154
155
|
): AsyncGenerator<AgentEvent>;
|
|
155
|
-
}
|
|
156
|
+
}
|
package/src/components/App.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
2
|
import type { KeyEvent } from '@opentui/core';
|
|
3
3
|
import { isFirstRun, markFirstRunComplete } from '../utils/config';
|
|
4
4
|
import { useRenderer } from '@opentui/react';
|
|
@@ -10,6 +10,10 @@ import { CommandModal } from './CommandsModal';
|
|
|
10
10
|
import { Notification, type NotificationData } from './Notification';
|
|
11
11
|
import { exec } from 'child_process';
|
|
12
12
|
import { promisify } from 'util';
|
|
13
|
+
import { subscribeNotifications } from '../utils/notificationBridge';
|
|
14
|
+
import { shouldRequireApprovals, setRequireApprovals } from '../utils/config';
|
|
15
|
+
import { getCurrentApproval, respondApproval } from '../utils/approvalBridge';
|
|
16
|
+
import { emitApprovalMode } from '../utils/approvalModeBridge';
|
|
13
17
|
|
|
14
18
|
const execAsync = promisify(exec);
|
|
15
19
|
|
|
@@ -28,7 +32,8 @@ export function App({ initialMessage }: AppProps) {
|
|
|
28
32
|
const [shortcutsTab, setShortcutsTab] = useState<0 | 1>(0);
|
|
29
33
|
const [commandsOpen, setCommandsOpen] = useState(false);
|
|
30
34
|
const [notifications, setNotifications] = useState<NotificationData[]>([]);
|
|
31
|
-
const [pendingMessage
|
|
35
|
+
const [pendingMessage] = useState<string | undefined>(initialMessage);
|
|
36
|
+
const lastSelectionRef = useRef<{ text: string; at: number } | null>(null);
|
|
32
37
|
|
|
33
38
|
const renderer = useRenderer();
|
|
34
39
|
|
|
@@ -41,11 +46,11 @@ export function App({ initialMessage }: AppProps) {
|
|
|
41
46
|
setNotifications(prev => prev.filter(n => n.id !== id));
|
|
42
47
|
}, []);
|
|
43
48
|
|
|
44
|
-
const copyToClipboard = async (text: string) => {
|
|
49
|
+
const copyToClipboard = useCallback(async (text: string) => {
|
|
45
50
|
try {
|
|
46
51
|
if (process.platform === 'win32') {
|
|
47
|
-
const
|
|
48
|
-
await execAsync(`powershell -
|
|
52
|
+
const encoded = Buffer.from(text, 'utf8').toString('base64');
|
|
53
|
+
await execAsync(`powershell.exe -NoProfile -Command "$bytes=[Convert]::FromBase64String('${encoded}');$text=[System.Text.Encoding]::UTF8.GetString($bytes);Set-Clipboard -Value $text"`);
|
|
49
54
|
} else if (process.platform === 'darwin') {
|
|
50
55
|
await execAsync(`echo ${JSON.stringify(text)} | pbcopy`);
|
|
51
56
|
} else {
|
|
@@ -54,7 +59,33 @@ export function App({ initialMessage }: AppProps) {
|
|
|
54
59
|
} catch (error) {
|
|
55
60
|
console.error('Failed to copy to clipboard:', error);
|
|
56
61
|
}
|
|
57
|
-
};
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const handler = (selection: any) => {
|
|
66
|
+
if (!selection || selection.isSelecting || !selection.isActive) return;
|
|
67
|
+
const text = typeof selection.getSelectedText === 'function' ? selection.getSelectedText() : '';
|
|
68
|
+
if (!text) return;
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const last = lastSelectionRef.current;
|
|
71
|
+
if (last && last.text === text && now - last.at < 400) return;
|
|
72
|
+
lastSelectionRef.current = { text, at: now };
|
|
73
|
+
copyToClipboard(text);
|
|
74
|
+
addNotification('Copied to clipboard', 'info', 2000);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const rendererAny = renderer as any;
|
|
78
|
+
rendererAny.on?.('selection', handler);
|
|
79
|
+
return () => {
|
|
80
|
+
rendererAny.off?.('selection', handler);
|
|
81
|
+
};
|
|
82
|
+
}, [renderer, copyToClipboard, addNotification]);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
return subscribeNotifications((payload) => {
|
|
86
|
+
addNotification(payload.message, payload.type ?? 'info', payload.duration);
|
|
87
|
+
});
|
|
88
|
+
}, [addNotification]);
|
|
58
89
|
|
|
59
90
|
useEffect(() => {
|
|
60
91
|
const isDarwin = process.platform === 'darwin';
|
|
@@ -68,6 +99,17 @@ export function App({ initialMessage }: AppProps) {
|
|
|
68
99
|
return;
|
|
69
100
|
}
|
|
70
101
|
|
|
102
|
+
if (k.name === 'tab' && k.shift) {
|
|
103
|
+
const next = !shouldRequireApprovals();
|
|
104
|
+
setRequireApprovals(next);
|
|
105
|
+
if (!next && getCurrentApproval()) {
|
|
106
|
+
respondApproval(true);
|
|
107
|
+
}
|
|
108
|
+
emitApprovalMode(next);
|
|
109
|
+
addNotification(next ? 'Approvals enabled' : 'Auto-approve enabled', 'info', 2500);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
71
113
|
if (shortcutsOpen && (k.name === 'f1' || k.name === 'f2')) {
|
|
72
114
|
setShortcutsTab(k.name === 'f2' ? 1 : 0);
|
|
73
115
|
return;
|
|
@@ -90,7 +132,7 @@ export function App({ initialMessage }: AppProps) {
|
|
|
90
132
|
return;
|
|
91
133
|
}
|
|
92
134
|
|
|
93
|
-
if (k.name === 'c' && !k.shift && (
|
|
135
|
+
if (k.name === 'c' && !k.shift && ((isDarwin && k.meta && !k.ctrl && !k.alt) || (!isDarwin && k.alt && !k.ctrl))) {
|
|
94
136
|
setCopyRequestId(prev => prev + 1);
|
|
95
137
|
return;
|
|
96
138
|
}
|
|
@@ -171,4 +213,4 @@ export function App({ initialMessage }: AppProps) {
|
|
|
171
213
|
);
|
|
172
214
|
}
|
|
173
215
|
|
|
174
|
-
export default App;
|
|
216
|
+
export default App;
|