skyloom 1.4.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 (225) hide show
  1. package/.github/workflows/ci.yml +36 -0
  2. package/CONVERSION_PLAN.md +191 -0
  3. package/README.md +67 -0
  4. package/dist/agents/dew.d.ts +15 -0
  5. package/dist/agents/dew.d.ts.map +1 -0
  6. package/dist/agents/dew.js +74 -0
  7. package/dist/agents/dew.js.map +1 -0
  8. package/dist/agents/fair.d.ts +15 -0
  9. package/dist/agents/fair.d.ts.map +1 -0
  10. package/dist/agents/fair.js +106 -0
  11. package/dist/agents/fair.js.map +1 -0
  12. package/dist/agents/fog.d.ts +15 -0
  13. package/dist/agents/fog.d.ts.map +1 -0
  14. package/dist/agents/fog.js +52 -0
  15. package/dist/agents/fog.js.map +1 -0
  16. package/dist/agents/frost.d.ts +15 -0
  17. package/dist/agents/frost.d.ts.map +1 -0
  18. package/dist/agents/frost.js +54 -0
  19. package/dist/agents/frost.js.map +1 -0
  20. package/dist/agents/rain.d.ts +15 -0
  21. package/dist/agents/rain.d.ts.map +1 -0
  22. package/dist/agents/rain.js +54 -0
  23. package/dist/agents/rain.js.map +1 -0
  24. package/dist/agents/snow.d.ts +27 -0
  25. package/dist/agents/snow.d.ts.map +1 -0
  26. package/dist/agents/snow.js +226 -0
  27. package/dist/agents/snow.js.map +1 -0
  28. package/dist/cli/main.d.ts +7 -0
  29. package/dist/cli/main.d.ts.map +1 -0
  30. package/dist/cli/main.js +402 -0
  31. package/dist/cli/main.js.map +1 -0
  32. package/dist/cli/mode.d.ts +17 -0
  33. package/dist/cli/mode.d.ts.map +1 -0
  34. package/dist/cli/mode.js +56 -0
  35. package/dist/cli/mode.js.map +1 -0
  36. package/dist/core/agent.d.ts +174 -0
  37. package/dist/core/agent.d.ts.map +1 -0
  38. package/dist/core/agent.js +1332 -0
  39. package/dist/core/agent.js.map +1 -0
  40. package/dist/core/agent_helpers.d.ts +51 -0
  41. package/dist/core/agent_helpers.d.ts.map +1 -0
  42. package/dist/core/agent_helpers.js +477 -0
  43. package/dist/core/agent_helpers.js.map +1 -0
  44. package/dist/core/bus.d.ts +99 -0
  45. package/dist/core/bus.d.ts.map +1 -0
  46. package/dist/core/bus.js +191 -0
  47. package/dist/core/bus.js.map +1 -0
  48. package/dist/core/cache.d.ts +63 -0
  49. package/dist/core/cache.d.ts.map +1 -0
  50. package/dist/core/cache.js +121 -0
  51. package/dist/core/cache.js.map +1 -0
  52. package/dist/core/checkpoint.d.ts +19 -0
  53. package/dist/core/checkpoint.d.ts.map +1 -0
  54. package/dist/core/checkpoint.js +120 -0
  55. package/dist/core/checkpoint.js.map +1 -0
  56. package/dist/core/circuit_breaker.d.ts +46 -0
  57. package/dist/core/circuit_breaker.d.ts.map +1 -0
  58. package/dist/core/circuit_breaker.js +99 -0
  59. package/dist/core/circuit_breaker.js.map +1 -0
  60. package/dist/core/config.d.ts +97 -0
  61. package/dist/core/config.d.ts.map +1 -0
  62. package/dist/core/config.js +281 -0
  63. package/dist/core/config.js.map +1 -0
  64. package/dist/core/constants.d.ts +78 -0
  65. package/dist/core/constants.d.ts.map +1 -0
  66. package/dist/core/constants.js +84 -0
  67. package/dist/core/constants.js.map +1 -0
  68. package/dist/core/factory.d.ts +63 -0
  69. package/dist/core/factory.d.ts.map +1 -0
  70. package/dist/core/factory.js +537 -0
  71. package/dist/core/factory.js.map +1 -0
  72. package/dist/core/icons.d.ts +28 -0
  73. package/dist/core/icons.d.ts.map +1 -0
  74. package/dist/core/icons.js +86 -0
  75. package/dist/core/icons.js.map +1 -0
  76. package/dist/core/index.d.ts +29 -0
  77. package/dist/core/index.d.ts.map +1 -0
  78. package/dist/core/index.js +54 -0
  79. package/dist/core/index.js.map +1 -0
  80. package/dist/core/llm.d.ts +121 -0
  81. package/dist/core/llm.d.ts.map +1 -0
  82. package/dist/core/llm.js +532 -0
  83. package/dist/core/llm.js.map +1 -0
  84. package/dist/core/logger.d.ts +57 -0
  85. package/dist/core/logger.d.ts.map +1 -0
  86. package/dist/core/logger.js +122 -0
  87. package/dist/core/logger.js.map +1 -0
  88. package/dist/core/mcp.d.ts +190 -0
  89. package/dist/core/mcp.d.ts.map +1 -0
  90. package/dist/core/mcp.js +822 -0
  91. package/dist/core/mcp.js.map +1 -0
  92. package/dist/core/mcp_server.d.ts +26 -0
  93. package/dist/core/mcp_server.d.ts.map +1 -0
  94. package/dist/core/mcp_server.js +211 -0
  95. package/dist/core/mcp_server.js.map +1 -0
  96. package/dist/core/memory.d.ts +190 -0
  97. package/dist/core/memory.d.ts.map +1 -0
  98. package/dist/core/memory.js +988 -0
  99. package/dist/core/memory.js.map +1 -0
  100. package/dist/core/middleware.d.ts +114 -0
  101. package/dist/core/middleware.d.ts.map +1 -0
  102. package/dist/core/middleware.js +248 -0
  103. package/dist/core/middleware.js.map +1 -0
  104. package/dist/core/pipelines.d.ts +87 -0
  105. package/dist/core/pipelines.d.ts.map +1 -0
  106. package/dist/core/pipelines.js +301 -0
  107. package/dist/core/pipelines.js.map +1 -0
  108. package/dist/core/profile.d.ts +23 -0
  109. package/dist/core/profile.d.ts.map +1 -0
  110. package/dist/core/profile.js +289 -0
  111. package/dist/core/profile.js.map +1 -0
  112. package/dist/core/router.d.ts +24 -0
  113. package/dist/core/router.d.ts.map +1 -0
  114. package/dist/core/router.js +111 -0
  115. package/dist/core/router.js.map +1 -0
  116. package/dist/core/schemas.d.ts +82 -0
  117. package/dist/core/schemas.d.ts.map +1 -0
  118. package/dist/core/schemas.js +200 -0
  119. package/dist/core/schemas.js.map +1 -0
  120. package/dist/core/semantic.d.ts +92 -0
  121. package/dist/core/semantic.d.ts.map +1 -0
  122. package/dist/core/semantic.js +175 -0
  123. package/dist/core/semantic.js.map +1 -0
  124. package/dist/core/skill.d.ts +68 -0
  125. package/dist/core/skill.d.ts.map +1 -0
  126. package/dist/core/skill.js +350 -0
  127. package/dist/core/skill.js.map +1 -0
  128. package/dist/core/tool.d.ts +99 -0
  129. package/dist/core/tool.d.ts.map +1 -0
  130. package/dist/core/tool.js +341 -0
  131. package/dist/core/tool.js.map +1 -0
  132. package/dist/core/tool_router.d.ts +29 -0
  133. package/dist/core/tool_router.d.ts.map +1 -0
  134. package/dist/core/tool_router.js +172 -0
  135. package/dist/core/tool_router.js.map +1 -0
  136. package/dist/core/workspace.d.ts +48 -0
  137. package/dist/core/workspace.d.ts.map +1 -0
  138. package/dist/core/workspace.js +179 -0
  139. package/dist/core/workspace.js.map +1 -0
  140. package/dist/plugins/loader.d.ts +17 -0
  141. package/dist/plugins/loader.d.ts.map +1 -0
  142. package/dist/plugins/loader.js +96 -0
  143. package/dist/plugins/loader.js.map +1 -0
  144. package/dist/skills/loader.d.ts +9 -0
  145. package/dist/skills/loader.d.ts.map +1 -0
  146. package/dist/skills/loader.js +78 -0
  147. package/dist/skills/loader.js.map +1 -0
  148. package/dist/tools/builtin.d.ts +10 -0
  149. package/dist/tools/builtin.d.ts.map +1 -0
  150. package/dist/tools/builtin.js +414 -0
  151. package/dist/tools/builtin.js.map +1 -0
  152. package/dist/tools/computer.d.ts +12 -0
  153. package/dist/tools/computer.d.ts.map +1 -0
  154. package/dist/tools/computer.js +326 -0
  155. package/dist/tools/computer.js.map +1 -0
  156. package/dist/tools/delegate.d.ts +10 -0
  157. package/dist/tools/delegate.d.ts.map +1 -0
  158. package/dist/tools/delegate.js +45 -0
  159. package/dist/tools/delegate.js.map +1 -0
  160. package/dist/web/server.d.ts +5 -0
  161. package/dist/web/server.d.ts.map +1 -0
  162. package/dist/web/server.js +647 -0
  163. package/dist/web/server.js.map +1 -0
  164. package/dist/web/tts.d.ts +33 -0
  165. package/dist/web/tts.d.ts.map +1 -0
  166. package/dist/web/tts.js +69 -0
  167. package/dist/web/tts.js.map +1 -0
  168. package/package.json +60 -0
  169. package/scripts/install.js +48 -0
  170. package/scripts/link.js +10 -0
  171. package/setup.bat +79 -0
  172. package/skill-test-ty2fOA/test.md +10 -0
  173. package/src/agents/dew.ts +70 -0
  174. package/src/agents/fair.ts +102 -0
  175. package/src/agents/fog.ts +48 -0
  176. package/src/agents/frost.ts +50 -0
  177. package/src/agents/rain.ts +50 -0
  178. package/src/agents/snow.ts +239 -0
  179. package/src/cli/main.ts +405 -0
  180. package/src/cli/mode.ts +58 -0
  181. package/src/core/agent.ts +1506 -0
  182. package/src/core/agent_helpers.ts +461 -0
  183. package/src/core/bus.ts +221 -0
  184. package/src/core/cache.ts +153 -0
  185. package/src/core/checkpoint.ts +94 -0
  186. package/src/core/circuit_breaker.ts +119 -0
  187. package/src/core/config.ts +341 -0
  188. package/src/core/constants.ts +95 -0
  189. package/src/core/factory.ts +627 -0
  190. package/src/core/icons.ts +53 -0
  191. package/src/core/index.ts +31 -0
  192. package/src/core/llm.ts +724 -0
  193. package/src/core/logger.ts +144 -0
  194. package/src/core/mcp.ts +953 -0
  195. package/src/core/mcp_server.ts +176 -0
  196. package/src/core/memory.ts +1169 -0
  197. package/src/core/middleware.ts +350 -0
  198. package/src/core/pipelines.ts +424 -0
  199. package/src/core/profile.ts +255 -0
  200. package/src/core/router.ts +124 -0
  201. package/src/core/schemas.ts +282 -0
  202. package/src/core/semantic.ts +211 -0
  203. package/src/core/skill.ts +342 -0
  204. package/src/core/tool.ts +427 -0
  205. package/src/core/tool_router.ts +193 -0
  206. package/src/core/workspace.ts +150 -0
  207. package/src/plugins/loader.ts +66 -0
  208. package/src/skills/loader.ts +46 -0
  209. package/src/sql.js.d.ts +29 -0
  210. package/src/tools/builtin.ts +382 -0
  211. package/src/tools/computer.ts +269 -0
  212. package/src/tools/delegate.ts +49 -0
  213. package/src/web/server.ts +634 -0
  214. package/src/web/tts.ts +93 -0
  215. package/tests/bus.test.ts +121 -0
  216. package/tests/icons.test.ts +45 -0
  217. package/tests/router.test.ts +86 -0
  218. package/tests/schemas.test.ts +51 -0
  219. package/tests/semantic.test.ts +83 -0
  220. package/tests/setup.ts +10 -0
  221. package/tests/skill.test.ts +172 -0
  222. package/tests/tool.test.ts +108 -0
  223. package/tests/tool_router.test.ts +71 -0
  224. package/tsconfig.json +37 -0
  225. package/vitest.config.ts +17 -0
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Workspace auto-detection and lifecycle management.
3
+ *
4
+ * Rules:
5
+ * - First launch: auto-select best drive, create workspace/
6
+ * - Multi-drive: skip C:, pick drive with most free space
7
+ * - Single drive (C: only): use C:\\workspace
8
+ * - Unix: use ~/workspace
9
+ * - User can override via config workspace.path
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as os from 'os';
14
+ import * as path from 'path';
15
+
16
+ const WORKSPACE_SUBDIRS = ['files', 'output', 'temp'];
17
+
18
+ export interface DriveInfo {
19
+ letter: string;
20
+ path: string;
21
+ totalBytes: number;
22
+ freeBytes: number;
23
+ }
24
+
25
+ /**
26
+ * Enumerate fixed drives with free-space info.
27
+ */
28
+ function getDriveList(): DriveInfo[] {
29
+ const drives: DriveInfo[] = [];
30
+
31
+ if (process.platform === 'win32') {
32
+ for (let i = 0; i < 26; i++) {
33
+ const letter = String.fromCharCode(65 + i); // A-Z
34
+ const root = `${letter}:\\`;
35
+ if (!fs.existsSync(root)) continue;
36
+ try {
37
+ const stat = fs.statfsSync(root);
38
+ drives.push({
39
+ letter,
40
+ path: root,
41
+ totalBytes: stat.blocks * stat.bsize,
42
+ freeBytes: stat.bfree * stat.bsize,
43
+ });
44
+ } catch {
45
+ try {
46
+ // Fallback: just mark it available
47
+ drives.push({
48
+ letter,
49
+ path: root,
50
+ totalBytes: 0,
51
+ freeBytes: 0,
52
+ });
53
+ } catch {
54
+ continue;
55
+ }
56
+ }
57
+ }
58
+ } else {
59
+ // Unix — treat / as the single candidate
60
+ try {
61
+ const stat = fs.statfsSync('/');
62
+ drives.push({
63
+ letter: '',
64
+ path: '/',
65
+ totalBytes: stat.blocks * stat.bsize,
66
+ freeBytes: stat.bfree * stat.bsize,
67
+ });
68
+ } catch {
69
+ // Ignore
70
+ }
71
+ }
72
+
73
+ return drives;
74
+ }
75
+
76
+ /**
77
+ * Pick the best drive for the workspace directory.
78
+ *
79
+ * - Windows with multiple drives: skip C:, pick drive with most free bytes.
80
+ * - Windows with only C: → C:\\workspace.
81
+ * - Unix → ~/workspace.
82
+ */
83
+ export function detectBestWorkspaceRoot(): string {
84
+ const drives = getDriveList();
85
+
86
+ if (process.platform === 'win32') {
87
+ let candidates = drives.filter((d) => d.letter.toUpperCase() !== 'C');
88
+ if (candidates.length === 0) {
89
+ candidates = drives; // fallback to C:
90
+ }
91
+ // Sort by free space descending
92
+ candidates.sort((a, b) => b.freeBytes - a.freeBytes);
93
+ const best = candidates[0];
94
+ return path.join(best.path, 'workspace');
95
+ } else {
96
+ return path.join(os.homedir(), 'workspace');
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Resolve workspace path from config.
102
+ *
103
+ * - "auto" → call detectBestWorkspaceRoot()
104
+ * - explicit path → expand ~ and return
105
+ */
106
+ export function resolveWorkspacePath(configValue: string): string {
107
+ if (configValue.toLowerCase() === 'auto') {
108
+ return detectBestWorkspaceRoot();
109
+ }
110
+ return path.resolve(configValue.replace(/^~/, os.homedir()));
111
+ }
112
+
113
+ /**
114
+ * Create workspace directory tree on first use. Idempotent.
115
+ *
116
+ * Creates:
117
+ * workspace/
118
+ * ├── files/ # agent-generated files
119
+ * ├── output/ # task results, exports
120
+ * └── temp/ # scratch / ephemeral
121
+ *
122
+ * Returns the resolved workspace root path.
123
+ */
124
+ export function initWorkspace(root: string): string {
125
+ fs.mkdirSync(root, { recursive: true });
126
+ for (const sub of WORKSPACE_SUBDIRS) {
127
+ fs.mkdirSync(path.join(root, sub), { recursive: true });
128
+ }
129
+ // Touch a .workspace marker so tools can identify it
130
+ const marker = path.join(root, '.workspace');
131
+ if (!fs.existsSync(marker)) {
132
+ fs.writeFileSync(marker, `# Skyloom workspace — created automatically\npath: ${root}\n`, 'utf-8');
133
+ }
134
+ return root;
135
+ }
136
+
137
+ /**
138
+ * Human-readable byte count (e.g. 128.5 GB).
139
+ */
140
+ export function formatBytes(n: number): string {
141
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
142
+ let val = n;
143
+ for (const unit of units) {
144
+ if (Math.abs(val) < 1024.0) {
145
+ return `${val.toFixed(1)} ${unit}`;
146
+ }
147
+ val /= 1024.0;
148
+ }
149
+ return `${val.toFixed(1)} PB`;
150
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Plugin loader — loads external plugins that register additional tools.
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { ToolRegistry } from '../core/tool';
8
+ import { getLogger } from '../core/logger';
9
+
10
+ const log = getLogger('plugin-loader');
11
+
12
+ export class PluginLoader {
13
+ private toolRegistry: ToolRegistry;
14
+
15
+ constructor(toolRegistry: ToolRegistry) {
16
+ this.toolRegistry = toolRegistry;
17
+ }
18
+
19
+ /**
20
+ * Load plugins from specified directories.
21
+ */
22
+ loadFromDirectories(directories: string[]): number {
23
+ let total = 0;
24
+ for (const dir of directories) {
25
+ total += this.loadDirectory(dir);
26
+ }
27
+ return total;
28
+ }
29
+
30
+ /**
31
+ * Load a single plugin directory.
32
+ */
33
+ private loadDirectory(dir: string): number {
34
+ if (!fs.existsSync(dir)) {
35
+ log.debug('plugin_dir_not_found', { dir });
36
+ return 0;
37
+ }
38
+
39
+ let count = 0;
40
+ try {
41
+ const entries = fs.readdirSync(dir);
42
+ for (const entry of entries) {
43
+ const pluginPath = path.join(dir, entry);
44
+ if (!fs.statSync(pluginPath).isDirectory()) continue;
45
+
46
+ const pluginFile = path.join(pluginPath, 'index.js');
47
+ if (!fs.existsSync(pluginFile)) continue;
48
+
49
+ try {
50
+ const plugin = require(pluginFile);
51
+ if (typeof plugin.register === 'function') {
52
+ plugin.register(this.toolRegistry);
53
+ count++;
54
+ log.info('plugin_loaded', { name: entry });
55
+ }
56
+ } catch (e) {
57
+ log.warn('plugin_load_failed', { name: entry, error: String(e) });
58
+ }
59
+ }
60
+ } catch (e) {
61
+ log.warn('plugin_dir_scan_failed', { dir, error: String(e) });
62
+ }
63
+
64
+ return count;
65
+ }
66
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Skill loader — registers all built-in skills from skill definition files.
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { SkillRegistry } from '../core/skill';
8
+ import { getLogger } from '../core/logger';
9
+
10
+ const log = getLogger('skills-loader');
11
+
12
+ // Built-in skill directories to scan
13
+ const BUILTIN_SKILL_DIRS = [
14
+ path.join(__dirname, '..', '..', 'config', 'skills'),
15
+ path.join(__dirname, '..', '..', 'assets', 'builtin_skills'),
16
+ ];
17
+
18
+ /**
19
+ * Register all available skills from built-in directories.
20
+ */
21
+ export function registerAllSkills(registry: SkillRegistry): void {
22
+ for (const dir of BUILTIN_SKILL_DIRS) {
23
+ if (!fs.existsSync(dir)) continue;
24
+
25
+ try {
26
+ const entries = fs.readdirSync(dir);
27
+ for (const entry of entries) {
28
+ const skillDir = path.join(dir, entry);
29
+ if (!fs.statSync(skillDir).isDirectory()) continue;
30
+
31
+ const skillFile = path.join(skillDir, 'SKILL.md');
32
+ if (!fs.existsSync(skillFile)) continue;
33
+
34
+ const skill = registry.loadSkillsFromDirectory(skillDir);
35
+ if (skill.length > 0) {
36
+ log.debug('registered_skill', { name: entry, file: skillFile });
37
+ }
38
+ }
39
+ } catch (e) {
40
+ log.warn('skill_load_failed', { dir, error: String(e) });
41
+ }
42
+ }
43
+
44
+ const count = registry.listNames().length;
45
+ log.info('skills_loaded', { count });
46
+ }
@@ -0,0 +1,29 @@
1
+ declare module 'sql.js' {
2
+ interface SqlJsStatic {
3
+ Database: new (data?: ArrayLike<number> | Buffer | null) => Database;
4
+ }
5
+
6
+ interface QueryExecResult {
7
+ columns: string[];
8
+ values: any[][];
9
+ }
10
+
11
+ interface Statement {
12
+ bind(params?: any[]): boolean;
13
+ step(): boolean;
14
+ getAsObject(params?: object): Record<string, any>;
15
+ free(): boolean;
16
+ }
17
+
18
+ interface Database {
19
+ run(sql: string, params?: any[]): Database;
20
+ exec(sql: string): QueryExecResult[];
21
+ prepare(sql: string): Statement;
22
+ export(): Uint8Array;
23
+ close(): void;
24
+ }
25
+
26
+ export type { Database, SqlJsStatic, Statement };
27
+
28
+ export default function initSqlJs(opts?: any): Promise<SqlJsStatic>;
29
+ }
@@ -0,0 +1,382 @@
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 { execSync } = require('child_process');
156
+ const cmd = params.command as string;
157
+ const timeout = (params.timeout as number) || 30000;
158
+ try {
159
+ const result = execSync(cmd, { encoding: 'utf-8', timeout, maxBuffer: 10 * 1024 * 1024 });
160
+ return result || '(command produced no output)';
161
+ } catch (e: any) {
162
+ return `Error: ${e.message || e}`;
163
+ }
164
+ },
165
+ dangerous: true,
166
+ });
167
+
168
+ // ── HTTP Tools ──
169
+
170
+ registry.register({
171
+ name: 'http_get',
172
+ description: 'Make an HTTP GET request to a URL.',
173
+ parameters: [
174
+ { name: 'url', type: 'string', description: 'URL to fetch', required: true },
175
+ ],
176
+ handler: async (params) => {
177
+ try {
178
+ const response = await fetch(params.url as string);
179
+ const text = await response.text();
180
+ return `Status: ${response.status}\n\n${text.slice(0, 10000)}${text.length > 10000 ? '\n...[truncated]' : ''}`;
181
+ } catch (e) {
182
+ return `Error fetching URL: ${e}`;
183
+ }
184
+ },
185
+ });
186
+
187
+ // ── Task Management ──
188
+
189
+ registry.register({
190
+ name: 'task_done',
191
+ description: 'Signal that the current task is complete and provide a summary. Call this when you have finished the work.',
192
+ parameters: [
193
+ { name: 'summary', type: 'string', description: 'Summary of what was accomplished', required: false },
194
+ ],
195
+ handler: async (_params) => {
196
+ return '__TASK_DONE__';
197
+ },
198
+ cacheable: false,
199
+ });
200
+
201
+ // ── Search Tool ──
202
+
203
+ registry.register({
204
+ name: 'web_search',
205
+ description: 'Search the web for information. Returns search results with titles and snippets.',
206
+ parameters: [
207
+ { name: 'query', type: 'string', description: 'Search query', required: true },
208
+ ],
209
+ handler: async (params) => {
210
+ // Simplified web search using a basic approach
211
+ try {
212
+ const query = encodeURIComponent(params.query as string);
213
+ const url = `https://api.duckduckgo.com/?q=${query}&format=json`;
214
+ const response = await fetch(url);
215
+ const data = await response.json() as Record<string, any>;
216
+ const results: string[] = [];
217
+ if (data.AbstractText) results.push(`Abstract: ${data.AbstractText}`);
218
+ if (data.RelatedTopics) {
219
+ for (const topic of data.RelatedTopics.slice(0, 10)) {
220
+ if (topic.Text) results.push(`- ${topic.Text}`);
221
+ else if (topic.Topics) {
222
+ for (const sub of topic.Topics.slice(0, 5)) {
223
+ if (sub.Text) results.push(`- ${sub.Text}`);
224
+ }
225
+ }
226
+ }
227
+ }
228
+ return results.length > 0 ? results.join('\n') : 'No search results found.';
229
+ } catch (e) {
230
+ return `Search error: ${e}`;
231
+ }
232
+ },
233
+ });
234
+
235
+ // ── Memory Tools ──
236
+
237
+ registry.register({
238
+ name: 'remember_fact',
239
+ description: 'Store a fact about the user or project in long-term memory.',
240
+ parameters: [
241
+ { name: 'key', type: 'string', description: 'Fact key (snake_case)', required: true },
242
+ { name: 'value', type: 'string', description: 'Fact value', required: true },
243
+ { name: 'category', type: 'string', description: 'Category (e.g. user_pref, project_info)', required: false },
244
+ ],
245
+ handler: async (_params) => {
246
+ return 'Fact stored. (Memory integration at agent level.)';
247
+ },
248
+ });
249
+
250
+ registry.register({
251
+ name: 'recall_facts',
252
+ description: 'Recall stored facts from long-term memory.',
253
+ parameters: [
254
+ { name: 'query', type: 'string', description: 'Search query to match against stored facts', required: true },
255
+ ],
256
+ handler: async (_params) => {
257
+ return 'Recall handled at agent level.';
258
+ },
259
+ });
260
+
261
+ // ── Git Tools ──
262
+
263
+ registry.register({
264
+ name: 'git_status',
265
+ description: 'Show the working tree status.',
266
+ parameters: [],
267
+ handler: async () => {
268
+ const { execSync } = require('child_process');
269
+ try {
270
+ return execSync('git status', { encoding: 'utf-8' });
271
+ } catch (e: any) {
272
+ return `Error: ${e.message || e}`;
273
+ }
274
+ },
275
+ });
276
+
277
+ registry.register({
278
+ name: 'git_diff',
279
+ description: 'Show changes between commits, commit and working tree, etc.',
280
+ parameters: [
281
+ { name: 'staged', type: 'boolean', description: 'Show staged changes only', required: false },
282
+ ],
283
+ handler: async (params) => {
284
+ const { execSync } = require('child_process');
285
+ try {
286
+ const args = params.staged ? '--staged' : '';
287
+ return execSync(`git diff ${args}`, { encoding: 'utf-8' });
288
+ } catch (e: any) {
289
+ return `Error: ${e.message || e}`;
290
+ }
291
+ },
292
+ });
293
+
294
+ registry.register({
295
+ name: 'git_log',
296
+ description: 'Show commit logs.',
297
+ parameters: [
298
+ { name: 'max_count', type: 'number', description: 'Number of commits to show (default: 10)', required: false },
299
+ ],
300
+ handler: async (params) => {
301
+ const { execSync } = require('child_process');
302
+ try {
303
+ const n = (params.max_count as number) || 10;
304
+ return execSync(`git log --oneline -${n}`, { encoding: 'utf-8' });
305
+ } catch (e: any) {
306
+ return `Error: ${e.message || e}`;
307
+ }
308
+ },
309
+ });
310
+
311
+ registry.register({
312
+ name: 'git_commit',
313
+ description: 'Create a new commit with the given message.',
314
+ parameters: [
315
+ { name: 'message', type: 'string', description: 'Commit message', required: true },
316
+ ],
317
+ handler: async (params) => {
318
+ const { execSync } = require('child_process');
319
+ try {
320
+ const msg = params.message as string;
321
+ execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, { encoding: 'utf-8' });
322
+ return 'Commit created successfully.';
323
+ } catch (e: any) {
324
+ return `Error: ${e.message || e}`;
325
+ }
326
+ },
327
+ dangerous: true,
328
+ });
329
+
330
+ // ── Utility Tools ──
331
+
332
+ registry.register({
333
+ name: 'grep',
334
+ description: 'Search for a pattern in files using ripgrep or grep.',
335
+ parameters: [
336
+ { name: 'pattern', type: 'string', description: 'Regex pattern to search for', required: true },
337
+ { name: 'path', type: 'string', description: 'Directory to search in', required: false },
338
+ ],
339
+ handler: async (params) => {
340
+ const { execSync } = require('child_process');
341
+ const searchDir = params.path ? path.resolve(params.path as string) : process.cwd();
342
+ const pat = String(params.pattern || '');
343
+ try {
344
+ 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 });
345
+ return out;
346
+ } catch {
347
+ return 'No matches found.';
348
+ }
349
+ },
350
+ });
351
+
352
+ registry.register({
353
+ name: 'tree',
354
+ description: 'Display directory tree structure.',
355
+ parameters: [
356
+ { name: 'directory', type: 'string', description: 'Directory to show tree for', required: false },
357
+ { name: 'depth', type: 'number', description: 'Maximum depth (default: 3)', required: false },
358
+ ],
359
+ handler: async (params) => {
360
+ const { execSync } = require('child_process');
361
+ const treeDir = params.directory ? path.resolve(params.directory as string) : process.cwd();
362
+ const depth = (params.depth as number) || 3;
363
+ try {
364
+ const out = execSync('tree "' + treeDir + '" -L ' + depth + ' --charset=utf-8 2>/dev/null || echo "tree unavailable"', { encoding: 'utf-8' });
365
+ return out;
366
+ } catch {
367
+ return 'Directory tree unavailable.';
368
+ }
369
+ },
370
+ });
371
+
372
+ log.info('builtin_tools_registered', { count: registry.listNames().length });
373
+ }
374
+
375
+ let _httpClient: any = null;
376
+
377
+ export async function closeHttpClient(): Promise<void> {
378
+ if (_httpClient) {
379
+ try { await _httpClient.close?.(); } catch { /* ignore */ }
380
+ _httpClient = null;
381
+ }
382
+ }