skyloom 1.12.0 → 1.13.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 (135) hide show
  1. package/.github/workflows/ci.yml +36 -36
  2. package/README.md +142 -46
  3. package/config/default.yaml +43 -47
  4. package/config/models.yaml +155 -155
  5. package/config/providers.yaml +39 -39
  6. package/config/skills/api_integrator/SKILL.md +15 -15
  7. package/config/skills/arch_designer/SKILL.md +13 -13
  8. package/config/skills/ci_cd_manager/SKILL.md +14 -14
  9. package/config/skills/code_analysis/SKILL.md +13 -13
  10. package/config/skills/code_generator/SKILL.md +12 -12
  11. package/config/skills/code_reviewer/SKILL.md +13 -13
  12. package/config/skills/content_writer/SKILL.md +14 -14
  13. package/config/skills/data_transformer/SKILL.md +15 -15
  14. package/config/skills/document_analysis/SKILL.md +13 -13
  15. package/config/skills/emotional_companion/SKILL.md +15 -15
  16. package/config/skills/performance_checker/SKILL.md +14 -14
  17. package/config/skills/security_auditor/SKILL.md +14 -14
  18. package/config/skills/self_evolve/SKILL.md +13 -13
  19. package/config/skills/sys_operator/SKILL.md +15 -15
  20. package/config/skills/task_planner/SKILL.md +14 -14
  21. package/config/skills/web_research/SKILL.md +14 -14
  22. package/config/skills/workflow_designer/SKILL.md +13 -13
  23. package/dist/agents/dew.js +52 -52
  24. package/dist/agents/fair.js +84 -84
  25. package/dist/agents/fog.js +30 -30
  26. package/dist/agents/frost.js +32 -32
  27. package/dist/agents/rain.js +32 -32
  28. package/dist/agents/snow.js +68 -68
  29. package/dist/cli/main.js +103 -51
  30. package/dist/cli/main.js.map +1 -1
  31. package/dist/cli/tui.d.ts.map +1 -1
  32. package/dist/cli/tui.js +8 -1
  33. package/dist/cli/tui.js.map +1 -1
  34. package/dist/core/agent/task.d.ts +58 -0
  35. package/dist/core/agent/task.d.ts.map +1 -0
  36. package/dist/core/agent/task.js +83 -0
  37. package/dist/core/agent/task.js.map +1 -0
  38. package/dist/core/agent.d.ts +2 -45
  39. package/dist/core/agent.d.ts.map +1 -1
  40. package/dist/core/agent.js +61 -145
  41. package/dist/core/agent.js.map +1 -1
  42. package/dist/core/agent_helpers.d.ts +10 -0
  43. package/dist/core/agent_helpers.d.ts.map +1 -1
  44. package/dist/core/agent_helpers.js +39 -0
  45. package/dist/core/agent_helpers.js.map +1 -1
  46. package/dist/core/catalog.d.ts +71 -0
  47. package/dist/core/catalog.d.ts.map +1 -0
  48. package/dist/core/catalog.js +176 -0
  49. package/dist/core/catalog.js.map +1 -0
  50. package/dist/core/config.d.ts +8 -0
  51. package/dist/core/config.d.ts.map +1 -1
  52. package/dist/core/config.js +12 -4
  53. package/dist/core/config.js.map +1 -1
  54. package/dist/core/factory.js +16 -16
  55. package/dist/core/llm.d.ts +7 -0
  56. package/dist/core/llm.d.ts.map +1 -1
  57. package/dist/core/llm.js +139 -7
  58. package/dist/core/llm.js.map +1 -1
  59. package/dist/core/longdoc.js +5 -5
  60. package/dist/core/memory.d.ts.map +1 -1
  61. package/dist/core/memory.js +69 -62
  62. package/dist/core/memory.js.map +1 -1
  63. package/dist/core/theme.d.ts +46 -0
  64. package/dist/core/theme.d.ts.map +1 -0
  65. package/dist/core/theme.js +42 -0
  66. package/dist/core/theme.js.map +1 -0
  67. package/dist/web/server.js +542 -519
  68. package/dist/web/server.js.map +1 -1
  69. package/docs/AESTHETIC_DESIGN.md +144 -0
  70. package/docs/OPTIMIZATION_PLAN.md +178 -0
  71. package/package.json +60 -60
  72. package/scripts/install.js +48 -48
  73. package/scripts/link.js +10 -10
  74. package/setup.bat +79 -79
  75. package/skill-test-ty2fOA/test.md +10 -10
  76. package/src/agents/dew.ts +70 -70
  77. package/src/agents/fair.ts +102 -102
  78. package/src/agents/fog.ts +48 -48
  79. package/src/agents/frost.ts +50 -50
  80. package/src/agents/rain.ts +50 -50
  81. package/src/agents/snow.ts +239 -239
  82. package/src/cli/main.ts +425 -372
  83. package/src/cli/mode.ts +58 -58
  84. package/src/cli/tui.ts +272 -269
  85. package/src/core/agent/task.ts +100 -0
  86. package/src/core/agent.ts +1446 -1549
  87. package/src/core/agent_helpers.ts +496 -461
  88. package/src/core/arbitrate.ts +162 -162
  89. package/src/core/catalog.ts +178 -0
  90. package/src/core/checkpoint.ts +94 -94
  91. package/src/core/config.ts +20 -4
  92. package/src/core/estimate.ts +104 -104
  93. package/src/core/evolve.ts +191 -191
  94. package/src/core/factory.ts +627 -627
  95. package/src/core/filter.ts +103 -103
  96. package/src/core/graph.ts +156 -156
  97. package/src/core/icons.ts +53 -53
  98. package/src/core/index.ts +37 -37
  99. package/src/core/learn.ts +146 -146
  100. package/src/core/llm.ts +108 -5
  101. package/src/core/longdoc.ts +155 -155
  102. package/src/core/mcp_server.ts +176 -176
  103. package/src/core/memory.ts +1178 -1171
  104. package/src/core/profile.ts +255 -255
  105. package/src/core/router.ts +124 -124
  106. package/src/core/sandbox.ts +142 -142
  107. package/src/core/security.ts +243 -243
  108. package/src/core/skill.ts +342 -342
  109. package/src/core/theme.ts +65 -0
  110. package/src/core/tool_router.ts +193 -193
  111. package/src/core/vector.ts +152 -152
  112. package/src/core/workspace.ts +150 -150
  113. package/src/plugins/loader.ts +66 -66
  114. package/src/skills/loader.ts +46 -46
  115. package/src/sql.js.d.ts +29 -29
  116. package/src/tools/builtin.ts +380 -380
  117. package/src/tools/computer.ts +269 -269
  118. package/src/tools/delegate.ts +49 -49
  119. package/src/web/server.ts +660 -634
  120. package/src/web/tts.ts +93 -93
  121. package/tests/agent_helpers.test.ts +48 -0
  122. package/tests/bus.test.ts +121 -121
  123. package/tests/catalog.test.ts +86 -0
  124. package/tests/config.test.ts +41 -0
  125. package/tests/icons.test.ts +45 -45
  126. package/tests/memory.test.ts +147 -0
  127. package/tests/router.test.ts +86 -86
  128. package/tests/schemas.test.ts +51 -51
  129. package/tests/semantic.test.ts +83 -83
  130. package/tests/setup.ts +10 -10
  131. package/tests/skill.test.ts +172 -172
  132. package/tests/task.test.ts +60 -0
  133. package/tests/tool.test.ts +108 -108
  134. package/tests/tool_router.test.ts +71 -71
  135. package/vitest.config.ts +17 -17
@@ -1,380 +1,380 @@
1
- /**
2
- * Built-in tool registration — registers all default tools.
3
- */
4
-
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import type { ToolRegistry } from '../core/tool';
8
- import { getLogger } from '../core/logger';
9
- import { registerComputerTools } from './computer';
10
-
11
- const log = getLogger('builtin-tools');
12
-
13
- /**
14
- * Register all built-in tools into the given registry.
15
- */
16
- export function registerBuiltinTools(registry: ToolRegistry): void {
17
- // Register computer tools
18
- registerComputerTools(registry);
19
- // ── File Tools ──
20
-
21
- registry.register({
22
- name: 'read_file',
23
- description: 'Read the contents of a file at the given path. Use this to inspect files, check file contents, or verify writes.',
24
- parameters: [
25
- { name: 'path', type: 'string', description: 'Absolute or relative path to the file', required: true },
26
- ],
27
- handler: async (params) => {
28
- const filePath = path.resolve(params.path as string);
29
- if (!fs.existsSync(filePath)) return `Error: File not found: ${filePath}`;
30
- try {
31
- const content = fs.readFileSync(filePath, 'utf-8');
32
- return `Successfully read ${filePath} (${content.length} chars):\n${content}`;
33
- } catch (e) {
34
- return `Error reading file: ${e}`;
35
- }
36
- },
37
- });
38
-
39
- registry.register({
40
- name: 'write_file',
41
- description: 'Write content to a file at the given path. Creates directories if needed.',
42
- parameters: [
43
- { name: 'path', type: 'string', description: 'Absolute or relative path to write to', required: true },
44
- { name: 'content', type: 'string', description: 'Content to write to the file', required: true },
45
- ],
46
- handler: async (params) => {
47
- const filePath = path.resolve(params.path as string);
48
- try {
49
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
50
- fs.writeFileSync(filePath, params.content as string, 'utf-8');
51
- return `Successfully wrote ${Buffer.byteLength(params.content as string, 'utf-8')} bytes to ${filePath}`;
52
- } catch (e) {
53
- return `Error writing file: ${e}`;
54
- }
55
- },
56
- });
57
-
58
- registry.register({
59
- name: 'edit_file',
60
- description: 'Edit a file by replacing old_text with new_text. Use this for targeted edits.',
61
- parameters: [
62
- { name: 'path', type: 'string', description: 'Path to the file to edit', required: true },
63
- { name: 'old_text', type: 'string', description: 'Text to search for and replace', required: true },
64
- { name: 'new_text', type: 'string', description: 'Text to replace with', required: true },
65
- ],
66
- handler: async (params) => {
67
- const filePath = path.resolve(params.path as string);
68
- if (!fs.existsSync(filePath)) return `Error: File not found: ${filePath}`;
69
- try {
70
- let content = fs.readFileSync(filePath, 'utf-8');
71
- const oldText = params.old_text as string;
72
- const newText = params.new_text as string;
73
- if (!content.includes(oldText)) {
74
- return `Error: old_text not found in file. Searched for: ${oldText.slice(0, 50)}...`;
75
- }
76
- content = content.replace(oldText, newText);
77
- fs.writeFileSync(filePath, content, 'utf-8');
78
- return `Successfully edited ${filePath}`;
79
- } catch (e) {
80
- return `Error editing file: ${e}`;
81
- }
82
- },
83
- });
84
-
85
- registry.register({
86
- name: 'delete_file',
87
- description: 'Delete a file at the given path.',
88
- parameters: [
89
- { name: 'path', type: 'string', description: 'Path to the file to delete', required: true },
90
- ],
91
- handler: async (params) => {
92
- const filePath = path.resolve(params.path as string);
93
- if (!fs.existsSync(filePath)) return `Error: File not found: ${filePath}`;
94
- try {
95
- fs.unlinkSync(filePath);
96
- return `Successfully deleted ${filePath}`;
97
- } catch (e) {
98
- return `Error deleting file: ${e}`;
99
- }
100
- },
101
- });
102
-
103
- registry.register({
104
- name: 'list_directory',
105
- description: 'List files and directories at the given path.',
106
- parameters: [
107
- { name: 'path', type: 'string', description: 'Path to list', required: true },
108
- ],
109
- handler: async (params) => {
110
- const dirPath = path.resolve(params.path as string);
111
- if (!fs.existsSync(dirPath)) return `Error: Directory not found: ${dirPath}`;
112
- try {
113
- const entries = fs.readdirSync(dirPath);
114
- return entries.map(e => {
115
- const stat = fs.statSync(path.join(dirPath, e));
116
- return `${stat.isDirectory() ? '[DIR]' : '[FILE]'} ${e}`;
117
- }).join('\n');
118
- } catch (e) {
119
- return `Error listing directory: ${e}`;
120
- }
121
- },
122
- });
123
-
124
- registry.register({
125
- name: 'file_search',
126
- description: 'Search for files matching a glob pattern.',
127
- parameters: [
128
- { name: 'pattern', type: 'string', description: 'Glob pattern to match (e.g. "**/*.ts")', required: true },
129
- { name: 'directory', type: 'string', description: 'Directory to search in (default: cwd)', required: false },
130
- ],
131
- handler: async (params) => {
132
- const dir = params.directory ? path.resolve(params.directory as string) : process.cwd();
133
- const pattern = params.pattern as string;
134
- try {
135
- const { globSync } = require('glob');
136
- const results = globSync(pattern, { cwd: dir, nodir: true });
137
- if (results.length === 0) return 'No files found matching the pattern.';
138
- return results.slice(0, 200).join('\n') + (results.length > 200 ? `\n... and ${results.length - 200} more` : '');
139
- } catch (e) {
140
- return `Error searching files: ${e}`;
141
- }
142
- },
143
- });
144
-
145
- // ── Shell Tool ──
146
-
147
- registry.register({
148
- name: 'run_bash',
149
- description: 'Execute a shell command and return its output.',
150
- parameters: [
151
- { name: 'command', type: 'string', description: 'Command to execute', required: true },
152
- { name: 'timeout', type: 'number', description: 'Timeout in milliseconds (default: 30000)', required: false },
153
- ],
154
- handler: async (params) => {
155
- const cmd = params.command as string;
156
- const timeout = (params.timeout as number) || 30000;
157
- try {
158
- const { runInSandbox, formatSandboxResult } = require('../core/sandbox');
159
- const result = runInSandbox(cmd, { timeoutMs: timeout });
160
- return formatSandboxResult(result);
161
- } catch (e: any) { return `Error: ${e.message || e}`; }
162
- },
163
- dangerous: true,
164
- });
165
-
166
- // ── HTTP Tools ──
167
-
168
- registry.register({
169
- name: 'http_get',
170
- description: 'Make an HTTP GET request to a URL.',
171
- parameters: [
172
- { name: 'url', type: 'string', description: 'URL to fetch', required: true },
173
- ],
174
- handler: async (params) => {
175
- try {
176
- const response = await fetch(params.url as string);
177
- const text = await response.text();
178
- return `Status: ${response.status}\n\n${text.slice(0, 10000)}${text.length > 10000 ? '\n...[truncated]' : ''}`;
179
- } catch (e) {
180
- return `Error fetching URL: ${e}`;
181
- }
182
- },
183
- });
184
-
185
- // ── Task Management ──
186
-
187
- registry.register({
188
- name: 'task_done',
189
- description: 'Signal that the current task is complete and provide a summary. Call this when you have finished the work.',
190
- parameters: [
191
- { name: 'summary', type: 'string', description: 'Summary of what was accomplished', required: false },
192
- ],
193
- handler: async (_params) => {
194
- return '__TASK_DONE__';
195
- },
196
- cacheable: false,
197
- });
198
-
199
- // ── Search Tool ──
200
-
201
- registry.register({
202
- name: 'web_search',
203
- description: 'Search the web for information. Returns search results with titles and snippets.',
204
- parameters: [
205
- { name: 'query', type: 'string', description: 'Search query', required: true },
206
- ],
207
- handler: async (params) => {
208
- // Simplified web search using a basic approach
209
- try {
210
- const query = encodeURIComponent(params.query as string);
211
- const url = `https://api.duckduckgo.com/?q=${query}&format=json`;
212
- const response = await fetch(url);
213
- const data = await response.json() as Record<string, any>;
214
- const results: string[] = [];
215
- if (data.AbstractText) results.push(`Abstract: ${data.AbstractText}`);
216
- if (data.RelatedTopics) {
217
- for (const topic of data.RelatedTopics.slice(0, 10)) {
218
- if (topic.Text) results.push(`- ${topic.Text}`);
219
- else if (topic.Topics) {
220
- for (const sub of topic.Topics.slice(0, 5)) {
221
- if (sub.Text) results.push(`- ${sub.Text}`);
222
- }
223
- }
224
- }
225
- }
226
- return results.length > 0 ? results.join('\n') : 'No search results found.';
227
- } catch (e) {
228
- return `Search error: ${e}`;
229
- }
230
- },
231
- });
232
-
233
- // ── Memory Tools ──
234
-
235
- registry.register({
236
- name: 'remember_fact',
237
- description: 'Store a fact about the user or project in long-term memory.',
238
- parameters: [
239
- { name: 'key', type: 'string', description: 'Fact key (snake_case)', required: true },
240
- { name: 'value', type: 'string', description: 'Fact value', required: true },
241
- { name: 'category', type: 'string', description: 'Category (e.g. user_pref, project_info)', required: false },
242
- ],
243
- handler: async (_params) => {
244
- return 'Fact stored. (Memory integration at agent level.)';
245
- },
246
- });
247
-
248
- registry.register({
249
- name: 'recall_facts',
250
- description: 'Recall stored facts from long-term memory.',
251
- parameters: [
252
- { name: 'query', type: 'string', description: 'Search query to match against stored facts', required: true },
253
- ],
254
- handler: async (_params) => {
255
- return 'Recall handled at agent level.';
256
- },
257
- });
258
-
259
- // ── Git Tools ──
260
-
261
- registry.register({
262
- name: 'git_status',
263
- description: 'Show the working tree status.',
264
- parameters: [],
265
- handler: async () => {
266
- const { execSync } = require('child_process');
267
- try {
268
- return execSync('git status', { encoding: 'utf-8' });
269
- } catch (e: any) {
270
- return `Error: ${e.message || e}`;
271
- }
272
- },
273
- });
274
-
275
- registry.register({
276
- name: 'git_diff',
277
- description: 'Show changes between commits, commit and working tree, etc.',
278
- parameters: [
279
- { name: 'staged', type: 'boolean', description: 'Show staged changes only', required: false },
280
- ],
281
- handler: async (params) => {
282
- const { execSync } = require('child_process');
283
- try {
284
- const args = params.staged ? '--staged' : '';
285
- return execSync(`git diff ${args}`, { encoding: 'utf-8' });
286
- } catch (e: any) {
287
- return `Error: ${e.message || e}`;
288
- }
289
- },
290
- });
291
-
292
- registry.register({
293
- name: 'git_log',
294
- description: 'Show commit logs.',
295
- parameters: [
296
- { name: 'max_count', type: 'number', description: 'Number of commits to show (default: 10)', required: false },
297
- ],
298
- handler: async (params) => {
299
- const { execSync } = require('child_process');
300
- try {
301
- const n = (params.max_count as number) || 10;
302
- return execSync(`git log --oneline -${n}`, { encoding: 'utf-8' });
303
- } catch (e: any) {
304
- return `Error: ${e.message || e}`;
305
- }
306
- },
307
- });
308
-
309
- registry.register({
310
- name: 'git_commit',
311
- description: 'Create a new commit with the given message.',
312
- parameters: [
313
- { name: 'message', type: 'string', description: 'Commit message', required: true },
314
- ],
315
- handler: async (params) => {
316
- const { execSync } = require('child_process');
317
- try {
318
- const msg = params.message as string;
319
- execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, { encoding: 'utf-8' });
320
- return 'Commit created successfully.';
321
- } catch (e: any) {
322
- return `Error: ${e.message || e}`;
323
- }
324
- },
325
- dangerous: true,
326
- });
327
-
328
- // ── Utility Tools ──
329
-
330
- registry.register({
331
- name: 'grep',
332
- description: 'Search for a pattern in files using ripgrep or grep.',
333
- parameters: [
334
- { name: 'pattern', type: 'string', description: 'Regex pattern to search for', required: true },
335
- { name: 'path', type: 'string', description: 'Directory to search in', required: false },
336
- ],
337
- handler: async (params) => {
338
- const { execSync } = require('child_process');
339
- const searchDir = params.path ? path.resolve(params.path as string) : process.cwd();
340
- const pat = String(params.pattern || '');
341
- try {
342
- const out = execSync('rg -n ' + pat + ' ' + searchDir + ' 2>/dev/null || grep -rn ' + pat + ' ' + searchDir + ' 2>/dev/null || echo "No matches found"', { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
343
- return out;
344
- } catch {
345
- return 'No matches found.';
346
- }
347
- },
348
- });
349
-
350
- registry.register({
351
- name: 'tree',
352
- description: 'Display directory tree structure.',
353
- parameters: [
354
- { name: 'directory', type: 'string', description: 'Directory to show tree for', required: false },
355
- { name: 'depth', type: 'number', description: 'Maximum depth (default: 3)', required: false },
356
- ],
357
- handler: async (params) => {
358
- const { execSync } = require('child_process');
359
- const treeDir = params.directory ? path.resolve(params.directory as string) : process.cwd();
360
- const depth = (params.depth as number) || 3;
361
- try {
362
- const out = execSync('tree "' + treeDir + '" -L ' + depth + ' --charset=utf-8 2>/dev/null || echo "tree unavailable"', { encoding: 'utf-8' });
363
- return out;
364
- } catch {
365
- return 'Directory tree unavailable.';
366
- }
367
- },
368
- });
369
-
370
- log.info('builtin_tools_registered', { count: registry.listNames().length });
371
- }
372
-
373
- let _httpClient: any = null;
374
-
375
- export async function closeHttpClient(): Promise<void> {
376
- if (_httpClient) {
377
- try { await _httpClient.close?.(); } catch { /* ignore */ }
378
- _httpClient = null;
379
- }
380
- }
1
+ /**
2
+ * Built-in tool registration — registers all default tools.
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import type { ToolRegistry } from '../core/tool';
8
+ import { getLogger } from '../core/logger';
9
+ import { registerComputerTools } from './computer';
10
+
11
+ const log = getLogger('builtin-tools');
12
+
13
+ /**
14
+ * Register all built-in tools into the given registry.
15
+ */
16
+ export function registerBuiltinTools(registry: ToolRegistry): void {
17
+ // Register computer tools
18
+ registerComputerTools(registry);
19
+ // ── File Tools ──
20
+
21
+ registry.register({
22
+ name: 'read_file',
23
+ description: 'Read the contents of a file at the given path. Use this to inspect files, check file contents, or verify writes.',
24
+ parameters: [
25
+ { name: 'path', type: 'string', description: 'Absolute or relative path to the file', required: true },
26
+ ],
27
+ handler: async (params) => {
28
+ const filePath = path.resolve(params.path as string);
29
+ if (!fs.existsSync(filePath)) return `Error: File not found: ${filePath}`;
30
+ try {
31
+ const content = fs.readFileSync(filePath, 'utf-8');
32
+ return `Successfully read ${filePath} (${content.length} chars):\n${content}`;
33
+ } catch (e) {
34
+ return `Error reading file: ${e}`;
35
+ }
36
+ },
37
+ });
38
+
39
+ registry.register({
40
+ name: 'write_file',
41
+ description: 'Write content to a file at the given path. Creates directories if needed.',
42
+ parameters: [
43
+ { name: 'path', type: 'string', description: 'Absolute or relative path to write to', required: true },
44
+ { name: 'content', type: 'string', description: 'Content to write to the file', required: true },
45
+ ],
46
+ handler: async (params) => {
47
+ const filePath = path.resolve(params.path as string);
48
+ try {
49
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
50
+ fs.writeFileSync(filePath, params.content as string, 'utf-8');
51
+ return `Successfully wrote ${Buffer.byteLength(params.content as string, 'utf-8')} bytes to ${filePath}`;
52
+ } catch (e) {
53
+ return `Error writing file: ${e}`;
54
+ }
55
+ },
56
+ });
57
+
58
+ registry.register({
59
+ name: 'edit_file',
60
+ description: 'Edit a file by replacing old_text with new_text. Use this for targeted edits.',
61
+ parameters: [
62
+ { name: 'path', type: 'string', description: 'Path to the file to edit', required: true },
63
+ { name: 'old_text', type: 'string', description: 'Text to search for and replace', required: true },
64
+ { name: 'new_text', type: 'string', description: 'Text to replace with', required: true },
65
+ ],
66
+ handler: async (params) => {
67
+ const filePath = path.resolve(params.path as string);
68
+ if (!fs.existsSync(filePath)) return `Error: File not found: ${filePath}`;
69
+ try {
70
+ let content = fs.readFileSync(filePath, 'utf-8');
71
+ const oldText = params.old_text as string;
72
+ const newText = params.new_text as string;
73
+ if (!content.includes(oldText)) {
74
+ return `Error: old_text not found in file. Searched for: ${oldText.slice(0, 50)}...`;
75
+ }
76
+ content = content.replace(oldText, newText);
77
+ fs.writeFileSync(filePath, content, 'utf-8');
78
+ return `Successfully edited ${filePath}`;
79
+ } catch (e) {
80
+ return `Error editing file: ${e}`;
81
+ }
82
+ },
83
+ });
84
+
85
+ registry.register({
86
+ name: 'delete_file',
87
+ description: 'Delete a file at the given path.',
88
+ parameters: [
89
+ { name: 'path', type: 'string', description: 'Path to the file to delete', required: true },
90
+ ],
91
+ handler: async (params) => {
92
+ const filePath = path.resolve(params.path as string);
93
+ if (!fs.existsSync(filePath)) return `Error: File not found: ${filePath}`;
94
+ try {
95
+ fs.unlinkSync(filePath);
96
+ return `Successfully deleted ${filePath}`;
97
+ } catch (e) {
98
+ return `Error deleting file: ${e}`;
99
+ }
100
+ },
101
+ });
102
+
103
+ registry.register({
104
+ name: 'list_directory',
105
+ description: 'List files and directories at the given path.',
106
+ parameters: [
107
+ { name: 'path', type: 'string', description: 'Path to list', required: true },
108
+ ],
109
+ handler: async (params) => {
110
+ const dirPath = path.resolve(params.path as string);
111
+ if (!fs.existsSync(dirPath)) return `Error: Directory not found: ${dirPath}`;
112
+ try {
113
+ const entries = fs.readdirSync(dirPath);
114
+ return entries.map(e => {
115
+ const stat = fs.statSync(path.join(dirPath, e));
116
+ return `${stat.isDirectory() ? '[DIR]' : '[FILE]'} ${e}`;
117
+ }).join('\n');
118
+ } catch (e) {
119
+ return `Error listing directory: ${e}`;
120
+ }
121
+ },
122
+ });
123
+
124
+ registry.register({
125
+ name: 'file_search',
126
+ description: 'Search for files matching a glob pattern.',
127
+ parameters: [
128
+ { name: 'pattern', type: 'string', description: 'Glob pattern to match (e.g. "**/*.ts")', required: true },
129
+ { name: 'directory', type: 'string', description: 'Directory to search in (default: cwd)', required: false },
130
+ ],
131
+ handler: async (params) => {
132
+ const dir = params.directory ? path.resolve(params.directory as string) : process.cwd();
133
+ const pattern = params.pattern as string;
134
+ try {
135
+ const { globSync } = require('glob');
136
+ const results = globSync(pattern, { cwd: dir, nodir: true });
137
+ if (results.length === 0) return 'No files found matching the pattern.';
138
+ return results.slice(0, 200).join('\n') + (results.length > 200 ? `\n... and ${results.length - 200} more` : '');
139
+ } catch (e) {
140
+ return `Error searching files: ${e}`;
141
+ }
142
+ },
143
+ });
144
+
145
+ // ── Shell Tool ──
146
+
147
+ registry.register({
148
+ name: 'run_bash',
149
+ description: 'Execute a shell command and return its output.',
150
+ parameters: [
151
+ { name: 'command', type: 'string', description: 'Command to execute', required: true },
152
+ { name: 'timeout', type: 'number', description: 'Timeout in milliseconds (default: 30000)', required: false },
153
+ ],
154
+ handler: async (params) => {
155
+ const cmd = params.command as string;
156
+ const timeout = (params.timeout as number) || 30000;
157
+ try {
158
+ const { runInSandbox, formatSandboxResult } = require('../core/sandbox');
159
+ const result = runInSandbox(cmd, { timeoutMs: timeout });
160
+ return formatSandboxResult(result);
161
+ } catch (e: any) { return `Error: ${e.message || e}`; }
162
+ },
163
+ dangerous: true,
164
+ });
165
+
166
+ // ── HTTP Tools ──
167
+
168
+ registry.register({
169
+ name: 'http_get',
170
+ description: 'Make an HTTP GET request to a URL.',
171
+ parameters: [
172
+ { name: 'url', type: 'string', description: 'URL to fetch', required: true },
173
+ ],
174
+ handler: async (params) => {
175
+ try {
176
+ const response = await fetch(params.url as string);
177
+ const text = await response.text();
178
+ return `Status: ${response.status}\n\n${text.slice(0, 10000)}${text.length > 10000 ? '\n...[truncated]' : ''}`;
179
+ } catch (e) {
180
+ return `Error fetching URL: ${e}`;
181
+ }
182
+ },
183
+ });
184
+
185
+ // ── Task Management ──
186
+
187
+ registry.register({
188
+ name: 'task_done',
189
+ description: 'Signal that the current task is complete and provide a summary. Call this when you have finished the work.',
190
+ parameters: [
191
+ { name: 'summary', type: 'string', description: 'Summary of what was accomplished', required: false },
192
+ ],
193
+ handler: async (_params) => {
194
+ return '__TASK_DONE__';
195
+ },
196
+ cacheable: false,
197
+ });
198
+
199
+ // ── Search Tool ──
200
+
201
+ registry.register({
202
+ name: 'web_search',
203
+ description: 'Search the web for information. Returns search results with titles and snippets.',
204
+ parameters: [
205
+ { name: 'query', type: 'string', description: 'Search query', required: true },
206
+ ],
207
+ handler: async (params) => {
208
+ // Simplified web search using a basic approach
209
+ try {
210
+ const query = encodeURIComponent(params.query as string);
211
+ const url = `https://api.duckduckgo.com/?q=${query}&format=json`;
212
+ const response = await fetch(url);
213
+ const data = await response.json() as Record<string, any>;
214
+ const results: string[] = [];
215
+ if (data.AbstractText) results.push(`Abstract: ${data.AbstractText}`);
216
+ if (data.RelatedTopics) {
217
+ for (const topic of data.RelatedTopics.slice(0, 10)) {
218
+ if (topic.Text) results.push(`- ${topic.Text}`);
219
+ else if (topic.Topics) {
220
+ for (const sub of topic.Topics.slice(0, 5)) {
221
+ if (sub.Text) results.push(`- ${sub.Text}`);
222
+ }
223
+ }
224
+ }
225
+ }
226
+ return results.length > 0 ? results.join('\n') : 'No search results found.';
227
+ } catch (e) {
228
+ return `Search error: ${e}`;
229
+ }
230
+ },
231
+ });
232
+
233
+ // ── Memory Tools ──
234
+
235
+ registry.register({
236
+ name: 'remember_fact',
237
+ description: 'Store a fact about the user or project in long-term memory.',
238
+ parameters: [
239
+ { name: 'key', type: 'string', description: 'Fact key (snake_case)', required: true },
240
+ { name: 'value', type: 'string', description: 'Fact value', required: true },
241
+ { name: 'category', type: 'string', description: 'Category (e.g. user_pref, project_info)', required: false },
242
+ ],
243
+ handler: async (_params) => {
244
+ return 'Fact stored. (Memory integration at agent level.)';
245
+ },
246
+ });
247
+
248
+ registry.register({
249
+ name: 'recall_facts',
250
+ description: 'Recall stored facts from long-term memory.',
251
+ parameters: [
252
+ { name: 'query', type: 'string', description: 'Search query to match against stored facts', required: true },
253
+ ],
254
+ handler: async (_params) => {
255
+ return 'Recall handled at agent level.';
256
+ },
257
+ });
258
+
259
+ // ── Git Tools ──
260
+
261
+ registry.register({
262
+ name: 'git_status',
263
+ description: 'Show the working tree status.',
264
+ parameters: [],
265
+ handler: async () => {
266
+ const { execSync } = require('child_process');
267
+ try {
268
+ return execSync('git status', { encoding: 'utf-8' });
269
+ } catch (e: any) {
270
+ return `Error: ${e.message || e}`;
271
+ }
272
+ },
273
+ });
274
+
275
+ registry.register({
276
+ name: 'git_diff',
277
+ description: 'Show changes between commits, commit and working tree, etc.',
278
+ parameters: [
279
+ { name: 'staged', type: 'boolean', description: 'Show staged changes only', required: false },
280
+ ],
281
+ handler: async (params) => {
282
+ const { execSync } = require('child_process');
283
+ try {
284
+ const args = params.staged ? '--staged' : '';
285
+ return execSync(`git diff ${args}`, { encoding: 'utf-8' });
286
+ } catch (e: any) {
287
+ return `Error: ${e.message || e}`;
288
+ }
289
+ },
290
+ });
291
+
292
+ registry.register({
293
+ name: 'git_log',
294
+ description: 'Show commit logs.',
295
+ parameters: [
296
+ { name: 'max_count', type: 'number', description: 'Number of commits to show (default: 10)', required: false },
297
+ ],
298
+ handler: async (params) => {
299
+ const { execSync } = require('child_process');
300
+ try {
301
+ const n = (params.max_count as number) || 10;
302
+ return execSync(`git log --oneline -${n}`, { encoding: 'utf-8' });
303
+ } catch (e: any) {
304
+ return `Error: ${e.message || e}`;
305
+ }
306
+ },
307
+ });
308
+
309
+ registry.register({
310
+ name: 'git_commit',
311
+ description: 'Create a new commit with the given message.',
312
+ parameters: [
313
+ { name: 'message', type: 'string', description: 'Commit message', required: true },
314
+ ],
315
+ handler: async (params) => {
316
+ const { execSync } = require('child_process');
317
+ try {
318
+ const msg = params.message as string;
319
+ execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, { encoding: 'utf-8' });
320
+ return 'Commit created successfully.';
321
+ } catch (e: any) {
322
+ return `Error: ${e.message || e}`;
323
+ }
324
+ },
325
+ dangerous: true,
326
+ });
327
+
328
+ // ── Utility Tools ──
329
+
330
+ registry.register({
331
+ name: 'grep',
332
+ description: 'Search for a pattern in files using ripgrep or grep.',
333
+ parameters: [
334
+ { name: 'pattern', type: 'string', description: 'Regex pattern to search for', required: true },
335
+ { name: 'path', type: 'string', description: 'Directory to search in', required: false },
336
+ ],
337
+ handler: async (params) => {
338
+ const { execSync } = require('child_process');
339
+ const searchDir = params.path ? path.resolve(params.path as string) : process.cwd();
340
+ const pat = String(params.pattern || '');
341
+ try {
342
+ const out = execSync('rg -n ' + pat + ' ' + searchDir + ' 2>/dev/null || grep -rn ' + pat + ' ' + searchDir + ' 2>/dev/null || echo "No matches found"', { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
343
+ return out;
344
+ } catch {
345
+ return 'No matches found.';
346
+ }
347
+ },
348
+ });
349
+
350
+ registry.register({
351
+ name: 'tree',
352
+ description: 'Display directory tree structure.',
353
+ parameters: [
354
+ { name: 'directory', type: 'string', description: 'Directory to show tree for', required: false },
355
+ { name: 'depth', type: 'number', description: 'Maximum depth (default: 3)', required: false },
356
+ ],
357
+ handler: async (params) => {
358
+ const { execSync } = require('child_process');
359
+ const treeDir = params.directory ? path.resolve(params.directory as string) : process.cwd();
360
+ const depth = (params.depth as number) || 3;
361
+ try {
362
+ const out = execSync('tree "' + treeDir + '" -L ' + depth + ' --charset=utf-8 2>/dev/null || echo "tree unavailable"', { encoding: 'utf-8' });
363
+ return out;
364
+ } catch {
365
+ return 'Directory tree unavailable.';
366
+ }
367
+ },
368
+ });
369
+
370
+ log.info('builtin_tools_registered', { count: registry.listNames().length });
371
+ }
372
+
373
+ let _httpClient: any = null;
374
+
375
+ export async function closeHttpClient(): Promise<void> {
376
+ if (_httpClient) {
377
+ try { await _httpClient.close?.(); } catch { /* ignore */ }
378
+ _httpClient = null;
379
+ }
380
+ }