xibecode 1.0.2 → 1.0.6

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 (288) hide show
  1. package/dist/commands/chat.d.ts +0 -1
  2. package/dist/commands/chat.d.ts.map +1 -1
  3. package/dist/commands/chat.js +10 -7
  4. package/dist/commands/chat.js.map +1 -1
  5. package/dist/commands/config.d.ts.map +1 -1
  6. package/dist/commands/config.js +5 -3
  7. package/dist/commands/config.js.map +1 -1
  8. package/dist/commands/diagnostics.js +1 -1
  9. package/dist/commands/diagnostics.js.map +1 -1
  10. package/dist/commands/mcp.js +1 -1
  11. package/dist/commands/mcp.js.map +1 -1
  12. package/dist/commands/resume.js +1 -1
  13. package/dist/commands/resume.js.map +1 -1
  14. package/dist/commands/run-pr.d.ts.map +1 -1
  15. package/dist/commands/run-pr.js +13 -10
  16. package/dist/commands/run-pr.js.map +1 -1
  17. package/dist/commands/run.d.ts.map +1 -1
  18. package/dist/commands/run.js +17 -14
  19. package/dist/commands/run.js.map +1 -1
  20. package/dist/commands/skills.d.ts.map +1 -1
  21. package/dist/commands/skills.js +3 -2
  22. package/dist/commands/skills.js.map +1 -1
  23. package/dist/components/AssistantMarkdown.js +1 -1
  24. package/dist/components/AssistantMarkdown.js.map +1 -1
  25. package/dist/index.js +2 -39
  26. package/dist/index.js.map +1 -1
  27. package/dist/ui/claude-style-chat.d.ts.map +1 -1
  28. package/dist/ui/claude-style-chat.js +15 -11
  29. package/dist/ui/claude-style-chat.js.map +1 -1
  30. package/dist/utils/built-in-skills-dir.d.ts +7 -0
  31. package/dist/utils/built-in-skills-dir.d.ts.map +1 -0
  32. package/dist/utils/built-in-skills-dir.js +11 -0
  33. package/dist/utils/built-in-skills-dir.js.map +1 -0
  34. package/dist/utils/config.d.ts +2 -119
  35. package/dist/utils/config.d.ts.map +1 -1
  36. package/dist/utils/config.js +3 -88
  37. package/dist/utils/config.js.map +1 -1
  38. package/package.json +11 -26
  39. package/dist/commands/punycode.d.ts +0 -5
  40. package/dist/commands/punycode.d.ts.map +0 -1
  41. package/dist/commands/punycode.js +0 -48
  42. package/dist/commands/punycode.js.map +0 -1
  43. package/dist/commands/tui.d.ts +0 -9
  44. package/dist/commands/tui.d.ts.map +0 -1
  45. package/dist/commands/tui.js +0 -83
  46. package/dist/commands/tui.js.map +0 -1
  47. package/dist/core/agent-tool-policies.d.ts +0 -5
  48. package/dist/core/agent-tool-policies.d.ts.map +0 -1
  49. package/dist/core/agent-tool-policies.js +0 -18
  50. package/dist/core/agent-tool-policies.js.map +0 -1
  51. package/dist/core/agent.d.ts +0 -181
  52. package/dist/core/agent.d.ts.map +0 -1
  53. package/dist/core/agent.js +0 -1777
  54. package/dist/core/agent.js.map +0 -1
  55. package/dist/core/background-agent.d.ts +0 -23
  56. package/dist/core/background-agent.d.ts.map +0 -1
  57. package/dist/core/background-agent.js +0 -175
  58. package/dist/core/background-agent.js.map +0 -1
  59. package/dist/core/code-graph.d.ts +0 -18
  60. package/dist/core/code-graph.d.ts.map +0 -1
  61. package/dist/core/code-graph.js +0 -105
  62. package/dist/core/code-graph.js.map +0 -1
  63. package/dist/core/conflict-solver.d.ts +0 -26
  64. package/dist/core/conflict-solver.d.ts.map +0 -1
  65. package/dist/core/conflict-solver.js +0 -108
  66. package/dist/core/conflict-solver.js.map +0 -1
  67. package/dist/core/context-compactor.d.ts +0 -10
  68. package/dist/core/context-compactor.d.ts.map +0 -1
  69. package/dist/core/context-compactor.js +0 -158
  70. package/dist/core/context-compactor.js.map +0 -1
  71. package/dist/core/context-pruner.d.ts +0 -19
  72. package/dist/core/context-pruner.d.ts.map +0 -1
  73. package/dist/core/context-pruner.js +0 -103
  74. package/dist/core/context-pruner.js.map +0 -1
  75. package/dist/core/context.d.ts +0 -82
  76. package/dist/core/context.d.ts.map +0 -1
  77. package/dist/core/context.js +0 -273
  78. package/dist/core/context.js.map +0 -1
  79. package/dist/core/conversation-recovery.d.ts +0 -9
  80. package/dist/core/conversation-recovery.d.ts.map +0 -1
  81. package/dist/core/conversation-recovery.js +0 -15
  82. package/dist/core/conversation-recovery.js.map +0 -1
  83. package/dist/core/debug-workflow.d.ts +0 -9
  84. package/dist/core/debug-workflow.d.ts.map +0 -1
  85. package/dist/core/debug-workflow.js +0 -19
  86. package/dist/core/debug-workflow.js.map +0 -1
  87. package/dist/core/docs-scraper.d.ts +0 -40
  88. package/dist/core/docs-scraper.d.ts.map +0 -1
  89. package/dist/core/docs-scraper.js +0 -386
  90. package/dist/core/docs-scraper.js.map +0 -1
  91. package/dist/core/editor.d.ts +0 -87
  92. package/dist/core/editor.d.ts.map +0 -1
  93. package/dist/core/editor.js +0 -377
  94. package/dist/core/editor.js.map +0 -1
  95. package/dist/core/export.d.ts +0 -11
  96. package/dist/core/export.d.ts.map +0 -1
  97. package/dist/core/export.js +0 -54
  98. package/dist/core/export.js.map +0 -1
  99. package/dist/core/history-manager.d.ts +0 -75
  100. package/dist/core/history-manager.d.ts.map +0 -1
  101. package/dist/core/history-manager.js +0 -146
  102. package/dist/core/history-manager.js.map +0 -1
  103. package/dist/core/marketplace-client.d.ts +0 -52
  104. package/dist/core/marketplace-client.d.ts.map +0 -1
  105. package/dist/core/marketplace-client.js +0 -71
  106. package/dist/core/marketplace-client.js.map +0 -1
  107. package/dist/core/mcp/mcp-config.d.ts +0 -10
  108. package/dist/core/mcp/mcp-config.d.ts.map +0 -1
  109. package/dist/core/mcp/mcp-config.js +0 -70
  110. package/dist/core/mcp/mcp-config.js.map +0 -1
  111. package/dist/core/mcp/mcp-policy.d.ts +0 -17
  112. package/dist/core/mcp/mcp-policy.d.ts.map +0 -1
  113. package/dist/core/mcp/mcp-policy.js +0 -56
  114. package/dist/core/mcp/mcp-policy.js.map +0 -1
  115. package/dist/core/mcp/oauth-flow.d.ts +0 -30
  116. package/dist/core/mcp/oauth-flow.d.ts.map +0 -1
  117. package/dist/core/mcp/oauth-flow.js +0 -230
  118. package/dist/core/mcp/oauth-flow.js.map +0 -1
  119. package/dist/core/mcp/oauth-store.d.ts +0 -13
  120. package/dist/core/mcp/oauth-store.d.ts.map +0 -1
  121. package/dist/core/mcp/oauth-store.js +0 -68
  122. package/dist/core/mcp/oauth-store.js.map +0 -1
  123. package/dist/core/mcp/resolve-mcp-servers.d.ts +0 -16
  124. package/dist/core/mcp/resolve-mcp-servers.d.ts.map +0 -1
  125. package/dist/core/mcp/resolve-mcp-servers.js +0 -83
  126. package/dist/core/mcp/resolve-mcp-servers.js.map +0 -1
  127. package/dist/core/mcp-client.d.ts +0 -99
  128. package/dist/core/mcp-client.d.ts.map +0 -1
  129. package/dist/core/mcp-client.js +0 -315
  130. package/dist/core/mcp-client.js.map +0 -1
  131. package/dist/core/memory-promotions.d.ts +0 -15
  132. package/dist/core/memory-promotions.d.ts.map +0 -1
  133. package/dist/core/memory-promotions.js +0 -38
  134. package/dist/core/memory-promotions.js.map +0 -1
  135. package/dist/core/memory.d.ts +0 -32
  136. package/dist/core/memory.d.ts.map +0 -1
  137. package/dist/core/memory.js +0 -121
  138. package/dist/core/memory.js.map +0 -1
  139. package/dist/core/modes.d.ts +0 -432
  140. package/dist/core/modes.d.ts.map +0 -1
  141. package/dist/core/modes.js +0 -1088
  142. package/dist/core/modes.js.map +0 -1
  143. package/dist/core/pattern-miner.d.ts +0 -43
  144. package/dist/core/pattern-miner.d.ts.map +0 -1
  145. package/dist/core/pattern-miner.js +0 -123
  146. package/dist/core/pattern-miner.js.map +0 -1
  147. package/dist/core/permission-store.d.ts +0 -15
  148. package/dist/core/permission-store.d.ts.map +0 -1
  149. package/dist/core/permission-store.js +0 -30
  150. package/dist/core/permission-store.js.map +0 -1
  151. package/dist/core/permissions.d.ts +0 -33
  152. package/dist/core/permissions.d.ts.map +0 -1
  153. package/dist/core/permissions.js +0 -141
  154. package/dist/core/permissions.js.map +0 -1
  155. package/dist/core/plan-artifacts.d.ts +0 -10
  156. package/dist/core/plan-artifacts.d.ts.map +0 -1
  157. package/dist/core/plan-artifacts.js +0 -60
  158. package/dist/core/plan-artifacts.js.map +0 -1
  159. package/dist/core/plan-session.d.ts +0 -25
  160. package/dist/core/plan-session.d.ts.map +0 -1
  161. package/dist/core/plan-session.js +0 -99
  162. package/dist/core/plan-session.js.map +0 -1
  163. package/dist/core/planMode.d.ts +0 -51
  164. package/dist/core/planMode.d.ts.map +0 -1
  165. package/dist/core/planMode.js +0 -245
  166. package/dist/core/planMode.js.map +0 -1
  167. package/dist/core/plugins.d.ts +0 -96
  168. package/dist/core/plugins.d.ts.map +0 -1
  169. package/dist/core/plugins.js +0 -202
  170. package/dist/core/plugins.js.map +0 -1
  171. package/dist/core/session-bridge.d.ts +0 -128
  172. package/dist/core/session-bridge.d.ts.map +0 -1
  173. package/dist/core/session-bridge.js +0 -328
  174. package/dist/core/session-bridge.js.map +0 -1
  175. package/dist/core/session-manager.d.ts +0 -80
  176. package/dist/core/session-manager.d.ts.map +0 -1
  177. package/dist/core/session-manager.js +0 -166
  178. package/dist/core/session-manager.js.map +0 -1
  179. package/dist/core/session-memory.d.ts +0 -45
  180. package/dist/core/session-memory.d.ts.map +0 -1
  181. package/dist/core/session-memory.js +0 -103
  182. package/dist/core/session-memory.js.map +0 -1
  183. package/dist/core/skill-selection.d.ts +0 -36
  184. package/dist/core/skill-selection.d.ts.map +0 -1
  185. package/dist/core/skill-selection.js +0 -172
  186. package/dist/core/skill-selection.js.map +0 -1
  187. package/dist/core/skills-sh-client.d.ts +0 -19
  188. package/dist/core/skills-sh-client.d.ts.map +0 -1
  189. package/dist/core/skills-sh-client.js +0 -75
  190. package/dist/core/skills-sh-client.js.map +0 -1
  191. package/dist/core/skills.d.ts +0 -97
  192. package/dist/core/skills.d.ts.map +0 -1
  193. package/dist/core/skills.js +0 -339
  194. package/dist/core/skills.js.map +0 -1
  195. package/dist/core/swarm.d.ts +0 -34
  196. package/dist/core/swarm.d.ts.map +0 -1
  197. package/dist/core/swarm.js +0 -111
  198. package/dist/core/swarm.js.map +0 -1
  199. package/dist/core/task-status.d.ts +0 -13
  200. package/dist/core/task-status.d.ts.map +0 -1
  201. package/dist/core/task-status.js +0 -17
  202. package/dist/core/task-status.js.map +0 -1
  203. package/dist/core/tool-orchestrator.d.ts +0 -30
  204. package/dist/core/tool-orchestrator.d.ts.map +0 -1
  205. package/dist/core/tool-orchestrator.js +0 -89
  206. package/dist/core/tool-orchestrator.js.map +0 -1
  207. package/dist/core/tools.d.ts +0 -462
  208. package/dist/core/tools.d.ts.map +0 -1
  209. package/dist/core/tools.js +0 -2916
  210. package/dist/core/tools.js.map +0 -1
  211. package/dist/core/transcript-cleanup.d.ts +0 -8
  212. package/dist/core/transcript-cleanup.d.ts.map +0 -1
  213. package/dist/core/transcript-cleanup.js +0 -52
  214. package/dist/core/transcript-cleanup.js.map +0 -1
  215. package/dist/core/visual-feedback.d.ts +0 -20
  216. package/dist/core/visual-feedback.d.ts.map +0 -1
  217. package/dist/core/visual-feedback.js +0 -117
  218. package/dist/core/visual-feedback.js.map +0 -1
  219. package/dist/tools/browser.d.ts +0 -120
  220. package/dist/tools/browser.d.ts.map +0 -1
  221. package/dist/tools/browser.js +0 -439
  222. package/dist/tools/browser.js.map +0 -1
  223. package/dist/tools/test-generator.d.ts +0 -157
  224. package/dist/tools/test-generator.d.ts.map +0 -1
  225. package/dist/tools/test-generator.js +0 -893
  226. package/dist/tools/test-generator.js.map +0 -1
  227. package/dist/tui/InkApp.d.ts +0 -21
  228. package/dist/tui/InkApp.d.ts.map +0 -1
  229. package/dist/tui/InkApp.js +0 -146
  230. package/dist/tui/InkApp.js.map +0 -1
  231. package/dist/tui/MarkdownMessage.d.ts +0 -16
  232. package/dist/tui/MarkdownMessage.d.ts.map +0 -1
  233. package/dist/tui/MarkdownMessage.js +0 -63
  234. package/dist/tui/MarkdownMessage.js.map +0 -1
  235. package/dist/tui/blessed-chat.d.ts +0 -9
  236. package/dist/tui/blessed-chat.d.ts.map +0 -1
  237. package/dist/tui/blessed-chat.js +0 -887
  238. package/dist/tui/blessed-chat.js.map +0 -1
  239. package/dist/tui/markdown-to-blessed.d.ts +0 -6
  240. package/dist/tui/markdown-to-blessed.d.ts.map +0 -1
  241. package/dist/tui/markdown-to-blessed.js +0 -26
  242. package/dist/tui/markdown-to-blessed.js.map +0 -1
  243. package/dist/ui/ink/App.d.ts +0 -25
  244. package/dist/ui/ink/App.d.ts.map +0 -1
  245. package/dist/ui/ink/App.js +0 -372
  246. package/dist/ui/ink/App.js.map +0 -1
  247. package/dist/utils/at-references.d.ts +0 -14
  248. package/dist/utils/at-references.d.ts.map +0 -1
  249. package/dist/utils/at-references.js +0 -47
  250. package/dist/utils/at-references.js.map +0 -1
  251. package/dist/utils/auto-memory.d.ts +0 -24
  252. package/dist/utils/auto-memory.d.ts.map +0 -1
  253. package/dist/utils/auto-memory.js +0 -153
  254. package/dist/utils/auto-memory.js.map +0 -1
  255. package/dist/utils/git.d.ts +0 -89
  256. package/dist/utils/git.d.ts.map +0 -1
  257. package/dist/utils/git.js +0 -444
  258. package/dist/utils/git.js.map +0 -1
  259. package/dist/utils/mcp-servers-file.d.ts +0 -46
  260. package/dist/utils/mcp-servers-file.d.ts.map +0 -1
  261. package/dist/utils/mcp-servers-file.js +0 -212
  262. package/dist/utils/mcp-servers-file.js.map +0 -1
  263. package/dist/utils/safety.d.ts +0 -60
  264. package/dist/utils/safety.d.ts.map +0 -1
  265. package/dist/utils/safety.js +0 -254
  266. package/dist/utils/safety.js.map +0 -1
  267. package/dist/utils/smithery.d.ts +0 -25
  268. package/dist/utils/smithery.d.ts.map +0 -1
  269. package/dist/utils/smithery.js +0 -50
  270. package/dist/utils/smithery.js.map +0 -1
  271. package/dist/utils/testRunner.d.ts +0 -44
  272. package/dist/utils/testRunner.d.ts.map +0 -1
  273. package/dist/utils/testRunner.js +0 -270
  274. package/dist/utils/testRunner.js.map +0 -1
  275. package/dist/webui/server.d.ts +0 -99
  276. package/dist/webui/server.d.ts.map +0 -1
  277. package/dist/webui/server.js +0 -2619
  278. package/dist/webui/server.js.map +0 -1
  279. package/webui-dist/assets/index-CSla6Lzy.css +0 -32
  280. package/webui-dist/assets/index-G_Z4gzPy.js +0 -457
  281. package/webui-dist/assets/index-G_Z4gzPy.js.map +0 -1
  282. package/webui-dist/assets/xterm-Da5jL1MD.js +0 -10
  283. package/webui-dist/assets/xterm-Da5jL1MD.js.map +0 -1
  284. package/webui-dist/assets/xterm-addon-fit-CMeqLIvm.js +0 -2
  285. package/webui-dist/assets/xterm-addon-fit-CMeqLIvm.js.map +0 -1
  286. package/webui-dist/assets/xterm-addon-web-links-D6m8jNVE.js +0 -2
  287. package/webui-dist/assets/xterm-addon-web-links-D6m8jNVE.js.map +0 -1
  288. package/webui-dist/index.html +0 -15
@@ -1,2916 +0,0 @@
1
- import * as fs from 'fs/promises';
2
- import * as path from 'path';
3
- import { exec, spawn } from 'child_process';
4
- import { promisify } from 'util';
5
- import { ContextManager } from './context.js';
6
- import { MODE_CONFIG, getToolCategory, isToolAllowed, isValidMode } from './modes.js';
7
- import { FileEditor } from './editor.js';
8
- import { GitUtils } from '../utils/git.js';
9
- import { TestRunnerDetector } from '../utils/testRunner.js';
10
- import { SafetyChecker, sanitizePath, sanitizeUrl } from '../utils/safety.js';
11
- import { PluginManager } from './plugins.js';
12
- import * as os from 'os';
13
- import { SkillManager } from './skills.js';
14
- import { TestGenerator, writeTestFile } from '../tools/test-generator.js';
15
- import { PatternMiner } from './pattern-miner.js';
16
- import { BackgroundAgentManager } from './background-agent.js';
17
- import { CodeGraph } from './code-graph.js';
18
- import { ConflictSolver } from './conflict-solver.js';
19
- import { SwarmOrchestrator } from './swarm.js';
20
- import { PermissionManager } from './permissions.js';
21
- import { McpOAuthFlowManager } from './mcp/oauth-flow.js';
22
- const execAsync = promisify(exec);
23
- const DEFAULT_COMMAND_OUTPUT_CHARS = 20000;
24
- function compactCommandOutput(value, maxChars) {
25
- if (value.length <= maxChars) {
26
- return { output: value, truncated: false, originalLength: value.length };
27
- }
28
- const marker = `\n\n[output truncated: kept head/tail, original length ${value.length} chars]\n\n`;
29
- const available = Math.max(0, maxChars - marker.length);
30
- const headLength = Math.ceil(available * 0.6);
31
- const tailLength = Math.floor(available * 0.4);
32
- return {
33
- output: `${value.slice(0, headLength)}${marker}${value.slice(value.length - tailLength)}`,
34
- truncated: true,
35
- originalLength: value.length,
36
- };
37
- }
38
- /** Returned by former Playwright-backed browser tools; XibeCode does not bundle browsers. */
39
- export const NO_EMBEDDED_BROWSER_MESSAGE = 'XibeCode does not bundle Playwright, Chromium, or agent-browser. Install agent-browser globally on supported OS/arch if you want it, or use your environment browser MCP / fetch_url. For Playwright E2E in a repo, add @playwright/test there and run it via run_command.';
40
- export const __testing = {
41
- compactCommandOutput,
42
- };
43
- /**
44
- * Main tool executor for XibeCode agent
45
- *
46
- * Provides 95+ tools across 8 categories for autonomous coding operations:
47
- * - File Operations: read, write, edit, delete files
48
- * - Git Operations: status, diff, commit, reset
49
- * - Shell Commands: run commands, interactive shell
50
- * - Web Operations: search, fetch URLs, HTTP requests
51
- * - Context Operations: code search, file finding, context discovery
52
- * - Test Operations: run tests, get results
53
- * - Memory Operations: update neural memory
54
- * - Browser guidance: use run_command + agent-browser (no bundled browser)
55
- *
56
- * Features:
57
- * - Mode-based tool permissions
58
- * - Safety checking for dangerous operations
59
- * - Plugin support for custom tools
60
- * - MCP integration for external tools
61
- * - Dry-run mode for safe previews
62
- * - Backup system for file operations
63
- *
64
- * @example
65
- * ```typescript
66
- * const executor = new CodingToolExecutor('/project', {
67
- * dryRun: false,
68
- * pluginManager,
69
- * memory
70
- * });
71
- *
72
- * executor.setMode('agent');
73
- *
74
- * // Read a file
75
- * const result = await executor.execute('read_file', { path: 'src/app.ts' });
76
- *
77
- * // Edit a file
78
- * await executor.execute('edit_file', {
79
- * path: 'src/app.ts',
80
- * search: 'old code',
81
- * replace: 'new code'
82
- * });
83
- * ```
84
- *
85
- * @category Tool Execution
86
- * @since 0.1.0
87
- */
88
- export class CodingToolExecutor {
89
- workingDir;
90
- contextManager;
91
- fileEditor;
92
- gitUtils;
93
- testRunner;
94
- safetyChecker;
95
- pluginManager;
96
- mcpClientManager;
97
- memory;
98
- skillManager;
99
- patternMiner;
100
- backgroundAgent;
101
- codeGraph;
102
- conflictSolver;
103
- swarmOrchestrator;
104
- platform;
105
- dryRun;
106
- testCommandOverride;
107
- permissionManager;
108
- /** Session-scoped tools synthesized by the agent (meta-agent). Execution is sandboxed via run_command. */
109
- dynamicTools = new Map();
110
- mcpOAuth = new McpOAuthFlowManager();
111
- /**
112
- * Creates a new CodingToolExecutor instance
113
- *
114
- * Initializes all tool subsystems including file operations, git utilities,
115
- * test runner, safety checker, plugin manager, and optional MCP integration.
116
- *
117
- * @example
118
- * ```typescript
119
- * // Basic usage
120
- * const executor = new CodingToolExecutor('/project');
121
- *
122
- * // With options
123
- * const executor = new CodingToolExecutor('/project', {
124
- * dryRun: true,
125
- * pluginManager: new PluginManager(),
126
- * memory: new NeuralMemory('./.xibecode/memory.json')
127
- * });
128
- * ```
129
- *
130
- * @param workingDir - Working directory for file operations (default: process.cwd())
131
- * @param options - Configuration options
132
- * @param options.dryRun - Enable dry-run mode (preview changes without executing)
133
- * @param options.testCommandOverride - Override test command detection
134
- * @param options.pluginManager - Plugin manager instance for custom tools
135
- * @param options.mcpClientManager - MCP client manager for external tool integration
136
- * @param options.memory - Neural memory instance for persistent learning
137
- * @param options.skillManager - Skill manager for loading custom workflows
138
- *
139
- * @category Constructor
140
- * @since 0.1.0
141
- */
142
- constructor(workingDir = process.cwd(), options) {
143
- this.workingDir = workingDir;
144
- this.contextManager = new ContextManager(workingDir);
145
- this.fileEditor = new FileEditor(workingDir);
146
- this.gitUtils = new GitUtils(workingDir);
147
- this.testRunner = new TestRunnerDetector(workingDir);
148
- this.safetyChecker = new SafetyChecker();
149
- this.pluginManager = options?.pluginManager || new PluginManager();
150
- this.mcpClientManager = options?.mcpClientManager;
151
- this.memory = options?.memory;
152
- this.patternMiner = new PatternMiner(workingDir);
153
- this.backgroundAgent = new BackgroundAgentManager(workingDir);
154
- this.codeGraph = new CodeGraph(workingDir);
155
- this.conflictSolver = new ConflictSolver(workingDir);
156
- this.swarmOrchestrator = new SwarmOrchestrator(this.backgroundAgent);
157
- // Initialize skill manager if provided, otherwise create a default one
158
- this.skillManager = options?.skillManager || new SkillManager(workingDir);
159
- this.platform = os.platform();
160
- this.dryRun = options?.dryRun || false;
161
- this.testCommandOverride = options?.testCommandOverride;
162
- this.permissionManager = options?.permissionManager ?? new PermissionManager(this.currentMode);
163
- }
164
- currentMode = 'agent';
165
- /**
166
- * Set the current agent mode
167
- *
168
- * Changes the tool executor's operating mode, which affects:
169
- * - Tool permissions (what tools are available)
170
- * - Dry-run default (some modes preview changes by default)
171
- * - Risk tolerance for operations
172
- *
173
- * @example
174
- * ```typescript
175
- * executor.setMode('plan'); // Read-only mode
176
- * executor.setMode('agent'); // Full capabilities
177
- * executor.setMode('security'); // Security-focused mode
178
- * ```
179
- *
180
- * @param mode - Agent mode to switch to
181
- *
182
- * @category Mode Management
183
- * @since 0.1.0
184
- */
185
- setMode(mode) {
186
- this.currentMode = mode;
187
- const config = MODE_CONFIG[mode];
188
- this.dryRun = config.defaultDryRun;
189
- this.permissionManager.setMode(mode);
190
- }
191
- setPermissionMode(permissionMode) {
192
- this.permissionManager.setPermissionMode(permissionMode);
193
- }
194
- getPermissionContext() {
195
- return this.permissionManager.getContext();
196
- }
197
- /**
198
- * Safely parse tool input - handles string JSON, null, undefined
199
- *
200
- * @param input - Raw tool input (string, object, null, or undefined)
201
- * @returns Parsed input as object
202
- *
203
- * @internal
204
- */
205
- parseInput(input) {
206
- if (!input)
207
- return {};
208
- if (typeof input === 'string') {
209
- try {
210
- return JSON.parse(input);
211
- }
212
- catch {
213
- return {};
214
- }
215
- }
216
- if (typeof input === 'object')
217
- return input;
218
- return {};
219
- }
220
- resolveToolCategory(toolName) {
221
- return getToolCategory(toolName);
222
- }
223
- /**
224
- * Execute a tool with given input
225
- *
226
- * Main tool execution pipeline that:
227
- * 1. Checks tool permissions for current mode
228
- * 2. Routes MCP tools to external servers
229
- * 3. Routes plugin tools to plugin manager
230
- * 4. Performs safety assessment for risky operations
231
- * 5. Executes the tool implementation
232
- * 6. Returns structured result
233
- *
234
- * @example
235
- * ```typescript
236
- * // Read a file
237
- * const result = await executor.execute('read_file', {
238
- * path: 'src/app.ts'
239
- * });
240
- *
241
- * if (result.success) {
242
- * console.log(result.content);
243
- * }
244
- *
245
- * // Edit a file
246
- * await executor.execute('edit_file', {
247
- * path: 'src/app.ts',
248
- * search: 'const old = 1;',
249
- * replace: 'const new = 2;'
250
- * });
251
- *
252
- * // Run a command
253
- * await executor.execute('run_command', {
254
- * command: 'npm test'
255
- * });
256
- * ```
257
- *
258
- * @param toolName - Name of the tool to execute (e.g., 'read_file', 'edit_file')
259
- * @param input - Tool input parameters (varies by tool)
260
- * @returns Tool execution result with success/error status
261
- *
262
- * @see {@link getTools} for list of available tools
263
- * @category Tool Execution
264
- * @since 0.1.0
265
- */
266
- async execute(toolName, input) {
267
- const p = this.parseInput(input);
268
- const category = this.resolveToolCategory(toolName);
269
- // Check tool permissions
270
- // Special exception: Allow writing implementations.md in plan mode
271
- let permission = isToolAllowed(this.currentMode, toolName);
272
- if (this.currentMode === 'plan' && toolName === 'write_file') {
273
- const isImplPlan = p.path && (p.path === 'implementations.md' || p.path.endsWith('/implementations.md'));
274
- if (isImplPlan) {
275
- permission = { allowed: true };
276
- }
277
- }
278
- if (this.currentMode === 'pentest' && toolName === 'write_file') {
279
- const isPentestReport = p.path && (p.path === 'pentest-report.md' || p.path.endsWith('/pentest-report.md'));
280
- if (isPentestReport) {
281
- permission = { allowed: true };
282
- }
283
- }
284
- if (!permission.allowed) {
285
- return {
286
- error: true,
287
- success: false,
288
- message: `PERMISSION DENIED: ${permission.reason}. Please delegate this task to the appropriate agent using [[REQUEST_MODE: <mode> | reason=...]].`,
289
- blocked: true
290
- };
291
- }
292
- const permissionDecision = this.permissionManager.evaluateToolExecution(p, toolName, category);
293
- if (!permissionDecision.allowed) {
294
- return {
295
- error: true,
296
- success: false,
297
- message: permissionDecision.reason ?? 'Permission denied by runtime permission manager',
298
- blocked: true,
299
- requiresApproval: permissionDecision.requiresApproval,
300
- };
301
- }
302
- if (category &&
303
- (p.confirm === true || p.approved === true) &&
304
- (p.approval_scope === 'session' || p.approval_scope === 'directory')) {
305
- this.permissionManager.grantToolApproval(toolName, category, p.approval_scope, typeof p.path === 'string' ? p.path : undefined);
306
- }
307
- // Check if it's an MCP tool (format: serverName::toolName)
308
- // Harden dispatch: only route if server is currently connected.
309
- if (this.mcpClientManager && toolName.includes('::')) {
310
- const serverName = toolName.split('::')[0];
311
- if (serverName && this.mcpClientManager.isConnected(serverName)) {
312
- try {
313
- const result = await this.mcpClientManager.executeMCPTool(toolName, p);
314
- return {
315
- success: true,
316
- ...result,
317
- };
318
- }
319
- catch (error) {
320
- return {
321
- error: true,
322
- success: false,
323
- message: error.message,
324
- };
325
- }
326
- }
327
- }
328
- // Check if it's a plugin tool
329
- if (this.pluginManager.isPluginTool(toolName)) {
330
- return this.pluginManager.executePluginTool(toolName, p);
331
- }
332
- // Safety assessment for risky operations
333
- const riskAssessment = this.safetyChecker.assessToolRisk(toolName, p);
334
- // Check for blocked commands in run_command
335
- if (toolName === 'run_command' && p.command) {
336
- const blockCheck = this.safetyChecker.isCommandBlocked(p.command);
337
- if (blockCheck.blocked) {
338
- return {
339
- error: true,
340
- success: false,
341
- message: `Command blocked: ${blockCheck.reason}`,
342
- blocked: true,
343
- };
344
- }
345
- // Suggest safer alternative if available
346
- const suggestion = this.safetyChecker.suggestSaferAlternative(p.command);
347
- if (suggestion && riskAssessment.level === 'high') {
348
- riskAssessment.warnings.push(`Suggestion: ${suggestion}`);
349
- }
350
- }
351
- try {
352
- if (this.dynamicTools.has(toolName)) {
353
- return this.runDynamicTool(toolName, p);
354
- }
355
- switch (toolName) {
356
- case 'read_file': {
357
- if (!p.path || typeof p.path !== 'string') {
358
- return { error: true, success: false, message: 'Missing required parameter: path (string). Example: {"path": "src/index.ts"}' };
359
- }
360
- return this.readFile(p.path, p.start_line, p.end_line);
361
- }
362
- case 'read_multiple_files': {
363
- if (!Array.isArray(p.paths) || p.paths.length === 0) {
364
- return { error: true, success: false, message: 'Missing required parameter: paths (non-empty array of strings). Example: {"paths": ["file1.ts", "file2.ts"]}' };
365
- }
366
- const validPaths = p.paths.filter((x) => typeof x === 'string');
367
- if (validPaths.length === 0) {
368
- return { error: true, success: false, message: 'paths array must contain strings. Example: {"paths": ["file1.ts", "file2.ts"]}' };
369
- }
370
- return this.readMultipleFiles(validPaths);
371
- }
372
- case 'write_file': {
373
- if (!p.path || typeof p.path !== 'string') {
374
- return { error: true, success: false, message: 'Missing required parameter: path (string)' };
375
- }
376
- if (typeof p.content !== 'string') {
377
- return { error: true, success: false, message: 'Missing required parameter: content (string)' };
378
- }
379
- return this.writeFile(p.path, p.content);
380
- }
381
- case 'edit_file': {
382
- if (!p.path || typeof p.path !== 'string') {
383
- return { error: true, success: false, message: 'Missing required parameter: path (string)' };
384
- }
385
- if (typeof p.search !== 'string') {
386
- return { error: true, success: false, message: 'Missing required parameter: search (string)' };
387
- }
388
- if (typeof p.replace !== 'string') {
389
- return { error: true, success: false, message: 'Missing required parameter: replace (string)' };
390
- }
391
- return this.editFile(p.path, p.search, p.replace, p.all);
392
- }
393
- case 'edit_lines': {
394
- if (!p.path || typeof p.path !== 'string') {
395
- return { error: true, success: false, message: 'Missing required parameter: path (string)' };
396
- }
397
- if (typeof p.start_line !== 'number' || typeof p.end_line !== 'number') {
398
- return { error: true, success: false, message: 'Missing required parameters: start_line, end_line (numbers)' };
399
- }
400
- if (typeof p.new_content !== 'string') {
401
- return { error: true, success: false, message: 'Missing required parameter: new_content (string)' };
402
- }
403
- return this.editLines(p.path, p.start_line, p.end_line, p.new_content);
404
- }
405
- case 'insert_at_line': {
406
- if (!p.path || typeof p.path !== 'string') {
407
- return { error: true, success: false, message: 'Missing required parameter: path (string)' };
408
- }
409
- if (typeof p.line !== 'number') {
410
- return { error: true, success: false, message: 'Missing required parameter: line (number)' };
411
- }
412
- return this.insertAtLine(p.path, p.line, p.content ?? '');
413
- }
414
- case 'verified_edit': {
415
- if (!p.path || typeof p.path !== 'string') {
416
- return { error: true, success: false, message: 'Missing required parameter: path (string)' };
417
- }
418
- if (typeof p.start_line !== 'number' || typeof p.end_line !== 'number') {
419
- return { error: true, success: false, message: 'Missing required parameters: start_line, end_line (numbers)' };
420
- }
421
- if (typeof p.old_content !== 'string') {
422
- return { error: true, success: false, message: 'Missing required parameter: old_content (string) - the content currently at those lines' };
423
- }
424
- if (typeof p.new_content !== 'string') {
425
- return { error: true, success: false, message: 'Missing required parameter: new_content (string)' };
426
- }
427
- return this.verifiedEditFile(p.path, p.start_line, p.end_line, p.old_content, p.new_content);
428
- }
429
- case 'list_directory':
430
- return this.listDirectory(p.path || '.');
431
- case 'search_files': {
432
- if (!p.pattern || typeof p.pattern !== 'string') {
433
- return { error: true, success: false, message: 'Missing required parameter: pattern (string). Example: {"pattern": "**/*.ts"}' };
434
- }
435
- return this.searchFiles(p.pattern, p.path);
436
- }
437
- case 'run_command': {
438
- if (!p.command || typeof p.command !== 'string') {
439
- return { error: true, success: false, message: 'Missing required parameter: command (string)' };
440
- }
441
- return this.runCommand(p.command, p.cwd, p.input, p.timeout, p.max_output_chars);
442
- }
443
- case 'create_directory': {
444
- if (!p.path || typeof p.path !== 'string') {
445
- return { error: true, success: false, message: 'Missing required parameter: path (string)' };
446
- }
447
- return this.createDirectory(p.path);
448
- }
449
- case 'delete_file': {
450
- if (!p.path || typeof p.path !== 'string') {
451
- return { error: true, success: false, message: 'Missing required parameter: path (string)' };
452
- }
453
- return this.deleteFile(p.path);
454
- }
455
- case 'move_file': {
456
- if (!p.source || typeof p.source !== 'string') {
457
- return { error: true, success: false, message: 'Missing required parameter: source (string)' };
458
- }
459
- if (!p.destination || typeof p.destination !== 'string') {
460
- return { error: true, success: false, message: 'Missing required parameter: destination (string)' };
461
- }
462
- return this.moveFile(p.source, p.destination);
463
- }
464
- case 'get_context': {
465
- if (!Array.isArray(p.files)) {
466
- return { error: true, success: false, message: 'Missing required parameter: files (array of strings)' };
467
- }
468
- return this.getContext(p.files.filter((f) => typeof f === 'string'));
469
- }
470
- case 'revert_file': {
471
- if (!p.path || typeof p.path !== 'string') {
472
- return { error: true, success: false, message: 'Missing required parameter: path (string)' };
473
- }
474
- return this.revertFile(p.path, p.backup_index);
475
- }
476
- case 'run_tests': {
477
- return this.runTests(p.command, p.cwd);
478
- }
479
- case 'get_test_status': {
480
- return this.getTestStatus();
481
- }
482
- case 'get_git_status': {
483
- return this.getGitStatus();
484
- }
485
- case 'get_git_diff_summary': {
486
- return this.getGitDiffSummary(p.target);
487
- }
488
- case 'get_git_changed_files': {
489
- return this.getGitChangedFiles(p.target);
490
- }
491
- case 'git_commit': {
492
- if (!p.message || typeof p.message !== 'string')
493
- return { error: true, success: false, message: 'Missing message' };
494
- return this.gitCommit(p.message, p.agent_name);
495
- }
496
- case 'git_blame_ai': {
497
- if (!p.file_path || typeof p.file_path !== 'string')
498
- return { error: true, success: false, message: 'Missing file_path' };
499
- return this.gitBlameAi(p.file_path);
500
- }
501
- case 'create_git_checkpoint': {
502
- if (!p.message || typeof p.message !== 'string') {
503
- return { error: true, success: false, message: 'Missing required parameter: message (string)' };
504
- }
505
- return this.createGitCheckpoint(p.message, p.strategy);
506
- }
507
- case 'revert_to_git_checkpoint': {
508
- if (!p.checkpoint_id || typeof p.checkpoint_id !== 'string') {
509
- return { error: true, success: false, message: 'Missing required parameter: checkpoint_id (string)' };
510
- }
511
- if (!p.confirm) {
512
- return { error: true, success: false, message: 'Revert requires explicit confirmation. Set confirm: true' };
513
- }
514
- return this.revertToGitCheckpoint(p.checkpoint_id, p.checkpoint_type, p.confirm);
515
- }
516
- case 'git_show_diff': {
517
- return this.gitShowDiff(p.file_path, p.target);
518
- }
519
- case 'mcp_list_resources': {
520
- if (!this.mcpClientManager)
521
- return { success: true, resources: [] };
522
- const resources = this.mcpClientManager.getAvailableResources().map((r) => ({
523
- server: r.serverName,
524
- uri: r.uri,
525
- name: r.name,
526
- mimeType: r.mimeType,
527
- description: r.description,
528
- }));
529
- return { success: true, resources, count: resources.length };
530
- }
531
- case 'mcp_read_resource': {
532
- if (!this.mcpClientManager)
533
- return { error: true, success: false, message: 'No MCP manager available' };
534
- if (!p.uri || typeof p.uri !== 'string')
535
- return { error: true, success: false, message: 'Missing uri (string)' };
536
- const result = await this.mcpClientManager.readResource(p.uri);
537
- return { success: true, ...result };
538
- }
539
- case 'mcp_list_prompts': {
540
- if (!this.mcpClientManager)
541
- return { success: true, prompts: [] };
542
- const prompts = this.mcpClientManager.getAvailablePrompts().map((pr) => ({
543
- server: pr.serverName,
544
- name: pr.name,
545
- description: pr.description,
546
- arguments: pr.arguments,
547
- }));
548
- return { success: true, prompts, count: prompts.length };
549
- }
550
- case 'mcp_get_prompt': {
551
- if (!this.mcpClientManager)
552
- return { error: true, success: false, message: 'No MCP manager available' };
553
- if (!p.name || typeof p.name !== 'string')
554
- return { error: true, success: false, message: 'Missing name (string, format server::promptName)' };
555
- const args = p.args && typeof p.args === 'object' ? p.args : undefined;
556
- const result = await this.mcpClientManager.getPrompt(p.name, args);
557
- return { success: true, ...result };
558
- }
559
- case 'mcp_auth': {
560
- return this.mcpAuth(p);
561
- }
562
- case 'get_mcp_status': {
563
- return this.getMCPStatus();
564
- }
565
- case 'grep_code': {
566
- if (!p.pattern || typeof p.pattern !== 'string') {
567
- return { error: true, success: false, message: 'Missing required parameter: pattern (string)' };
568
- }
569
- return this.grepCode(p.pattern, p.path, p.ignore_case, p.file_pattern, p.max_results);
570
- }
571
- case 'web_search': {
572
- if (!p.query || typeof p.query !== 'string') {
573
- return { error: true, success: false, message: 'Missing required parameter: query (string)' };
574
- }
575
- return this.webSearch(p.query, p.max_results);
576
- }
577
- case 'fetch_url': {
578
- if (!p.url || typeof p.url !== 'string') {
579
- return { error: true, success: false, message: 'Missing required parameter: url (string)' };
580
- }
581
- return this.fetchUrl(p.url, p.max_length);
582
- }
583
- case 'update_memory': {
584
- if (!p.content || typeof p.content !== 'string') {
585
- return { error: true, success: false, message: 'Missing required parameter: content (string)' };
586
- }
587
- return this.updateMemory(p.content, p.append);
588
- }
589
- case 'remember_lesson': {
590
- if (!this.memory) {
591
- return { error: true, success: false, message: 'Memory system is not initialized.' };
592
- }
593
- if (!p.trigger || typeof p.trigger !== 'string')
594
- return { error: true, success: false, message: 'Missing trigger' };
595
- if (!p.action || typeof p.action !== 'string')
596
- return { error: true, success: false, message: 'Missing action' };
597
- if (!p.outcome || typeof p.outcome !== 'string')
598
- return { error: true, success: false, message: 'Missing outcome' };
599
- await this.memory.addMemory(p.trigger, p.action, p.outcome, p.tags || []);
600
- return { success: true, message: 'Lesson learned and saved to neural memory.' };
601
- }
602
- case 'take_screenshot':
603
- case 'get_console_logs':
604
- case 'run_visual_test':
605
- case 'check_accessibility':
606
- case 'measure_performance':
607
- case 'test_responsive':
608
- case 'capture_network':
609
- case 'preview_app':
610
- return { error: true, success: false, message: NO_EMBEDDED_BROWSER_MESSAGE };
611
- case 'search_skills_sh': {
612
- if (!p.query || typeof p.query !== 'string') {
613
- return { error: true, success: false, message: 'Missing required parameter: query (string)' };
614
- }
615
- return this.searchSkillsSh(p.query);
616
- }
617
- case 'install_skill_from_skills_sh': {
618
- if (!p.skill_id || typeof p.skill_id !== 'string') {
619
- return { error: true, success: false, message: 'Missing required parameter: skill_id (string)' };
620
- }
621
- return this.installSkillFromSkillsSh(p.skill_id);
622
- }
623
- // AI Test Generation Tools
624
- case 'generate_tests': {
625
- if (!p.file_path || typeof p.file_path !== 'string') {
626
- return { error: true, success: false, message: 'Missing required parameter: file_path (string)' };
627
- }
628
- return this.generateTests(p.file_path, {
629
- framework: p.framework,
630
- outputDir: p.output_dir,
631
- includeEdgeCases: p.include_edge_cases,
632
- includeMocks: p.include_mocks,
633
- maxTestsPerFunction: p.max_tests_per_function,
634
- }, p.write_file);
635
- }
636
- case 'mine_project_patterns': {
637
- const patterns = await this.patternMiner.mine();
638
- if (patterns.length === 0) {
639
- return { success: true, message: 'No significant repeated patterns found in the project.' };
640
- }
641
- // Format for AI consumption
642
- const summary = patterns.map(p => `Pattern: ${p.description}\n` +
643
- `- Occurrences: ${p.frequency}\n` +
644
- `- Locations: ${p.chunks.map(c => `${path.relative(this.workingDir, c.filePath)}:${c.startLine}`).join(', ')}\n` +
645
- `- Example Code:\n\`\`\`typescript\n${p.chunks[0].content}\n\`\`\`\n`).join('\n---\n\n');
646
- return {
647
- success: true,
648
- message: `Found ${patterns.length} pattern clusters.`,
649
- patterns: summary
650
- };
651
- }
652
- case 'start_background_task': {
653
- if (!p.prompt || typeof p.prompt !== 'string') {
654
- return { error: true, success: false, message: 'Missing required parameter: prompt (string)' };
655
- }
656
- const taskId = await this.backgroundAgent.startTask(p.prompt);
657
- return { success: true, message: `Background task started with ID: ${taskId}`, task_id: taskId };
658
- }
659
- case 'list_background_tasks': {
660
- const tasks = await this.backgroundAgent.listTasks();
661
- const summary = tasks.map(t => `ID: ${t.id} | Status: ${t.status} | Started: ${new Date(t.startTime).toISOString()} | Prompt: "${t.prompt.substring(0, 50)}..."`).join('\n');
662
- return { success: true, message: `Active Tasks:\n${summary || 'No active tasks.'}`, tasks };
663
- }
664
- case 'check_background_task': {
665
- if (!p.task_id || typeof p.task_id !== 'string') {
666
- return { error: true, success: false, message: 'Missing required parameter: task_id (string)' };
667
- }
668
- const logs = await this.backgroundAgent.getTaskLogs(p.task_id);
669
- const task = await this.backgroundAgent.getTask(p.task_id);
670
- return { success: true, task, logs };
671
- }
672
- case 'start_background_task': {
673
- if (!p.prompt || typeof p.prompt !== 'string') {
674
- return { error: true, success: false, message: 'Missing required parameter: prompt (string)' };
675
- }
676
- const taskId = await this.backgroundAgent.startTask(p.prompt);
677
- return { success: true, message: `Background task started with ID: ${taskId}`, task_id: taskId };
678
- }
679
- case 'list_background_tasks': {
680
- const tasks = await this.backgroundAgent.listTasks();
681
- const summary = tasks.map(t => `ID: ${t.id} | Status: ${t.status} | Started: ${new Date(t.startTime).toISOString()} | Prompt: "${t.prompt.substring(0, 50)}..."`).join('\n');
682
- return { success: true, message: `Active Tasks:\n${summary || 'No active tasks.'}`, tasks };
683
- }
684
- case 'check_background_task': {
685
- if (!p.task_id || typeof p.task_id !== 'string') {
686
- return { error: true, success: false, message: 'Missing required parameter: task_id (string)' };
687
- }
688
- const logs = await this.backgroundAgent.getTaskLogs(p.task_id);
689
- const task = await this.backgroundAgent.getTask(p.task_id);
690
- return { success: true, task, logs };
691
- }
692
- case 'search_code_graph': {
693
- if (!p.query || typeof p.query !== 'string') {
694
- return { error: true, success: false, message: 'Missing required parameter: query (symbol name)' };
695
- }
696
- const results = await this.codeGraph.findReferences(p.query);
697
- return { success: true, message: results };
698
- }
699
- case 'analyze_code_for_tests': {
700
- if (!p.file_path || typeof p.file_path !== 'string') {
701
- return { error: true, success: false, message: 'Missing required parameter: file_path (string)' };
702
- }
703
- return this.analyzeCodeForTests(p.file_path);
704
- }
705
- case 'resolve_merge_conflicts': {
706
- const files = await this.conflictSolver.findConflictingFiles();
707
- if (files.length === 0) {
708
- return { success: true, message: 'No merge conflicts found in the project.' };
709
- }
710
- // If specific file requested, use it; otherwise default to first
711
- const targetFile = (p.file_path && typeof p.file_path === 'string') ? p.file_path : files[0];
712
- // Ensure target is in the list or we try to parse it anyway
713
- const conflictData = await this.conflictSolver.parseConflicts(targetFile);
714
- if (!conflictData) {
715
- return { success: false, message: `Could not parse conflicts in ${targetFile}. Markers might be missing or malformed.`, other_files: files };
716
- }
717
- return {
718
- success: true,
719
- message: `Found ${files.length} conflicting files. Showing conflicts for: ${targetFile}`,
720
- conflicts: conflictData.conflicts.map(c => ({
721
- id: c.index,
722
- lines: `${c.startLine}-${c.endLine}`,
723
- ours: c.ours,
724
- theirs: c.theirs,
725
- base: c.base
726
- })),
727
- other_conflicting_files: files.filter(f => f !== targetFile)
728
- };
729
- }
730
- case 'delegate_subtask': {
731
- if (!p.task || typeof p.task !== 'string')
732
- return { error: true, success: false, message: 'Missing task' };
733
- if (!p.worker_type || typeof p.worker_type !== 'string')
734
- return { error: true, success: false, message: 'Missing worker_type (agent mode)' };
735
- if (!isValidMode(p.worker_type)) {
736
- return { error: true, success: false, message: `Invalid worker_type: ${p.worker_type}. Must be a valid AgentMode.` };
737
- }
738
- const result = await this.swarmOrchestrator.delegateSubtask(p.worker_type, p.task);
739
- return {
740
- success: result.success,
741
- result: result.result,
742
- status: result.status,
743
- worker: p.worker_type
744
- };
745
- }
746
- case 'run_swarm': {
747
- const subtasks = p.subtasks;
748
- if (!Array.isArray(subtasks) || subtasks.length === 0) {
749
- return { error: true, success: false, message: 'run_swarm requires a non-empty subtasks array' };
750
- }
751
- const normalized = [];
752
- for (let i = 0; i < subtasks.length; i++) {
753
- const st = subtasks[i];
754
- if (!st || typeof st !== 'object') {
755
- return { error: true, success: false, message: `Invalid subtasks[${i}]: expected object` };
756
- }
757
- const task = st.task;
758
- const worker_type = st.worker_type;
759
- if (typeof task !== 'string' || !task.trim()) {
760
- return { error: true, success: false, message: `subtasks[${i}].task must be a non-empty string` };
761
- }
762
- if (typeof worker_type !== 'string' || !isValidMode(worker_type)) {
763
- return {
764
- error: true,
765
- success: false,
766
- message: `subtasks[${i}].worker_type must be a valid AgentMode (got ${String(worker_type)})`,
767
- };
768
- }
769
- normalized.push({ mode: worker_type, task: task.trim() });
770
- }
771
- const timeoutMs = typeof p.timeout_ms === 'number' && Number.isFinite(p.timeout_ms) && p.timeout_ms > 0
772
- ? Math.floor(p.timeout_ms)
773
- : undefined;
774
- const maxConcurrent = typeof p.max_parallel === 'number' && Number.isFinite(p.max_parallel) && p.max_parallel > 0
775
- ? Math.floor(p.max_parallel)
776
- : undefined;
777
- const results = await this.swarmOrchestrator.delegateSubtasksParallel(normalized, {
778
- timeoutMs,
779
- maxConcurrent,
780
- });
781
- const success = results.every((r) => r.success);
782
- return {
783
- success,
784
- parallel: true,
785
- results,
786
- message: success
787
- ? 'All swarm subtasks finished successfully.'
788
- : 'One or more swarm subtasks failed, timed out, or were killed.',
789
- };
790
- }
791
- case 'synthesize_tool': {
792
- const name = typeof p.name === 'string' ? p.name.trim() : '';
793
- const description = typeof p.description === 'string' ? p.description.trim() : '';
794
- const script = typeof p.script === 'string' ? p.script.trim() : '';
795
- if (!name || !script) {
796
- return { error: true, success: false, message: 'Missing required parameters: name (string), script (string). description is optional.' };
797
- }
798
- if (!/^[a-z][a-z0-9_]*$/.test(name)) {
799
- return { error: true, success: false, message: 'Tool name must be lowercase letters, numbers, underscores only (e.g. my_helper).' };
800
- }
801
- const reserved = new Set(['read_file', 'write_file', 'run_command', 'synthesize_tool', 'get_context']);
802
- if (reserved.has(name)) {
803
- return { error: true, success: false, message: `Cannot override built-in tool: ${name}` };
804
- }
805
- this.dynamicTools.set(name, { description: description || name, script });
806
- return { success: true, message: `Tool "${name}" registered. You can call it with the same name. Execution is sandboxed.` };
807
- }
808
- default:
809
- return { error: true, success: false, message: `Unknown tool: ${toolName}. Available tools: read_file, read_multiple_files, write_file, edit_file, edit_lines, insert_at_line, verified_edit, list_directory, search_files, run_command, create_directory, delete_file, move_file, get_context, revert_file, run_tests, get_test_status, get_git_status, get_git_diff_summary, get_git_changed_files, create_git_checkpoint, revert_to_git_checkpoint, git_show_diff, get_mcp_status, grep_code, web_search, fetch_url, remember_lesson, synthesize_tool, take_screenshot, get_console_logs, run_visual_test, check_accessibility, measure_performance, test_responsive, capture_network, search_skills_sh, install_skill_from_skills_sh, preview_app, delegate_subtask, run_swarm` };
810
- }
811
- }
812
- catch (err) {
813
- return { error: true, success: false, message: err?.message ?? String(err) };
814
- }
815
- }
816
- /**
817
- * Get all available tools for the agent
818
- *
819
- * Returns an array of tool definitions including core tools, plugin tools,
820
- * and MCP tools. Each tool includes:
821
- * - name: Tool identifier
822
- * - description: What the tool does (for Claude AI)
823
- * - input_schema: JSON Schema for input validation
824
- *
825
- * Tools are grouped by category:
826
- * - File Operations: read_file, write_file, edit_file, etc.
827
- * - Git Operations: get_git_status, git_commit, etc.
828
- * - Shell Commands: run_command, interactive_shell
829
- * - Web Operations: web_search, fetch_url, http_request
830
- * - Context Operations: grep_code, search_files, get_context
831
- * - Test Operations: run_tests, get_test_status
832
- * - Memory Operations: update_memory
833
- * - Browser Operations: open_browser, browser_click, etc.
834
- *
835
- * @example
836
- * ```typescript
837
- * const tools = executor.getTools();
838
- * console.log(`${tools.length} tools available`);
839
- *
840
- * // Find a specific tool
841
- * const readFile = tools.find(t => t.name === 'read_file');
842
- * console.log(readFile.description);
843
- * ```
844
- *
845
- * @returns Array of tool definitions with schemas
846
- *
847
- * @category Tool Management
848
- * @since 0.1.0
849
- */
850
- getTools() {
851
- const coreTools = [
852
- {
853
- name: 'read_file',
854
- description: 'Read file contents. For large files, can read specific line ranges to avoid token limits. Always use this before editing files.',
855
- input_schema: {
856
- type: 'object',
857
- properties: {
858
- path: {
859
- type: 'string',
860
- description: 'Path to the file to read',
861
- },
862
- start_line: {
863
- type: 'number',
864
- description: 'Optional: Start line number (1-indexed) for partial read',
865
- },
866
- end_line: {
867
- type: 'number',
868
- description: 'Optional: End line number for partial read',
869
- },
870
- },
871
- required: ['path'],
872
- },
873
- },
874
- {
875
- name: 'read_multiple_files',
876
- description: 'Read multiple files at once efficiently. Good for getting project context.',
877
- input_schema: {
878
- type: 'object',
879
- properties: {
880
- paths: {
881
- type: 'array',
882
- items: { type: 'string' },
883
- description: 'Array of file paths to read',
884
- },
885
- },
886
- required: ['paths'],
887
- },
888
- },
889
- {
890
- name: 'write_file',
891
- description: 'Write content to a file. Creates new file or overwrites existing. For editing existing files, prefer edit_file instead.',
892
- input_schema: {
893
- type: 'object',
894
- properties: {
895
- path: {
896
- type: 'string',
897
- description: 'Path to the file',
898
- },
899
- content: {
900
- type: 'string',
901
- description: 'Full content to write',
902
- },
903
- },
904
- required: ['path', 'content'],
905
- },
906
- },
907
- {
908
- name: 'edit_file',
909
- description: 'Edit file by searching for exact text and replacing it. MOST RELIABLE for making changes. The search string must be unique in the file.',
910
- input_schema: {
911
- type: 'object',
912
- properties: {
913
- path: {
914
- type: 'string',
915
- description: 'Path to file to edit',
916
- },
917
- search: {
918
- type: 'string',
919
- description: 'Exact text to find (must be unique)',
920
- },
921
- replace: {
922
- type: 'string',
923
- description: 'Text to replace it with',
924
- },
925
- all: {
926
- type: 'boolean',
927
- description: 'Replace all occurrences (default: false, requires unique match)',
928
- },
929
- },
930
- required: ['path', 'search', 'replace'],
931
- },
932
- },
933
- {
934
- name: 'edit_lines',
935
- description: 'Edit specific line range in a file. Good for large files when you know the line numbers.',
936
- input_schema: {
937
- type: 'object',
938
- properties: {
939
- path: {
940
- type: 'string',
941
- description: 'Path to file',
942
- },
943
- start_line: {
944
- type: 'number',
945
- description: 'Start line number (1-indexed)',
946
- },
947
- end_line: {
948
- type: 'number',
949
- description: 'End line number (inclusive)',
950
- },
951
- new_content: {
952
- type: 'string',
953
- description: 'New content to replace those lines',
954
- },
955
- },
956
- required: ['path', 'start_line', 'end_line', 'new_content'],
957
- },
958
- },
959
- {
960
- name: 'insert_at_line',
961
- description: 'Insert content at a specific line number without replacing existing content.',
962
- input_schema: {
963
- type: 'object',
964
- properties: {
965
- path: {
966
- type: 'string',
967
- description: 'Path to file',
968
- },
969
- line: {
970
- type: 'number',
971
- description: 'Line number to insert at (1-indexed)',
972
- },
973
- content: {
974
- type: 'string',
975
- description: 'Content to insert',
976
- },
977
- },
978
- required: ['path', 'line', 'content'],
979
- },
980
- },
981
- {
982
- name: 'verified_edit',
983
- description: 'MOST RELIABLE file editing tool. Edit a file by specifying the exact line range, the old content that should currently be at those lines (for verification), and the new content to replace it with. If old_content does not match what is actually in the file, the edit is REJECTED and the actual content is returned so you can retry. ALWAYS use read_file first to get the current content and line numbers before using this tool. This is PREFERRED over edit_file and edit_lines for accuracy.',
984
- input_schema: {
985
- type: 'object',
986
- properties: {
987
- path: {
988
- type: 'string',
989
- description: 'Path to file to edit',
990
- },
991
- start_line: {
992
- type: 'number',
993
- description: 'Start line number (1-indexed) of the content to replace',
994
- },
995
- end_line: {
996
- type: 'number',
997
- description: 'End line number (inclusive)',
998
- },
999
- old_content: {
1000
- type: 'string',
1001
- description: 'The content currently at those lines (for verification)',
1002
- },
1003
- new_content: {
1004
- type: 'string',
1005
- description: 'New content to replace those lines',
1006
- },
1007
- },
1008
- required: ['path', 'start_line', 'end_line', 'old_content', 'new_content'],
1009
- },
1010
- },
1011
- {
1012
- name: 'mine_project_patterns',
1013
- description: 'Analyze the project codebase to find repeated code patterns, structural duplication, and similar logic. Returns a list of pattern clusters that can be used to synthesize new skills.',
1014
- input_schema: {
1015
- type: 'object',
1016
- properties: {},
1017
- },
1018
- },
1019
- {
1020
- name: 'preview_app',
1021
- description: 'Disabled: no bundled browser. Returns guidance to use run_command with agent-browser or a browser MCP. Previously captured a screenshot and simplified DOM summary.',
1022
- input_schema: {
1023
- type: 'object',
1024
- properties: {
1025
- url: {
1026
- type: 'string',
1027
- description: 'URL of the application to preview',
1028
- },
1029
- full_page: {
1030
- type: 'boolean',
1031
- description: 'Capture full page screenshot instead of just viewport (default: false)',
1032
- },
1033
- },
1034
- required: ['url'],
1035
- },
1036
- },
1037
- {
1038
- name: 'start_background_task',
1039
- description: 'Start a long-running task in the background. The agent will run in a detached process.',
1040
- input_schema: {
1041
- type: 'object',
1042
- properties: {
1043
- prompt: {
1044
- type: 'string',
1045
- description: 'The task instructions for the background agent',
1046
- },
1047
- },
1048
- required: ['prompt'],
1049
- },
1050
- },
1051
- {
1052
- name: 'list_background_tasks',
1053
- description: 'List all running and completed background tasks.',
1054
- input_schema: {
1055
- type: 'object',
1056
- properties: {},
1057
- },
1058
- },
1059
- {
1060
- name: 'check_background_task',
1061
- description: 'Get the logs and status of a specific background task.',
1062
- input_schema: {
1063
- type: 'object',
1064
- properties: {
1065
- task_id: {
1066
- type: 'string',
1067
- description: 'The ID of the task to check',
1068
- },
1069
- },
1070
- required: ['task_id'],
1071
- },
1072
- },
1073
- {
1074
- name: 'search_code_graph',
1075
- description: 'Semantic code search using ts-morph. Finds where a symbol (class, function, variable) is defined and referenced in the project. More powerful than text search for code understanding.',
1076
- input_schema: {
1077
- type: 'object',
1078
- properties: {
1079
- query: { type: 'string', description: 'The symbol name to search for (e.g. "User", "authService")' }
1080
- },
1081
- required: ['query']
1082
- }
1083
- },
1084
- {
1085
- name: 'list_directory',
1086
- description: 'List files and directories in a path with metadata.',
1087
- input_schema: {
1088
- type: 'object',
1089
- properties: {
1090
- path: {
1091
- type: 'string',
1092
- description: 'Directory path (default: current directory)',
1093
- },
1094
- },
1095
- },
1096
- },
1097
- {
1098
- name: 'search_files',
1099
- description: 'Search for files matching a glob pattern. Cross-platform compatible.',
1100
- input_schema: {
1101
- type: 'object',
1102
- properties: {
1103
- pattern: {
1104
- type: 'string',
1105
- description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.js")',
1106
- },
1107
- path: {
1108
- type: 'string',
1109
- description: 'Base path to search from',
1110
- },
1111
- },
1112
- required: ['pattern'],
1113
- },
1114
- },
1115
- {
1116
- name: 'run_command',
1117
- description: `Execute shell command. Platform: ${this.platform}. Commands have a timeout (default 120s). IMPORTANT: Always use non-interactive flags when available (e.g. --yes, --default, -y). For interactive prompts, use the "input" parameter to send stdin (newline-separated answers). Example: npx create-next-app@latest myapp --yes --typescript --tailwind --app --use-pnpm`,
1118
- input_schema: {
1119
- type: 'object',
1120
- properties: {
1121
- command: {
1122
- type: 'string',
1123
- description: 'Command to execute. Prefer non-interactive flags like --yes, -y, --default to avoid prompts.',
1124
- },
1125
- cwd: {
1126
- type: 'string',
1127
- description: 'Working directory (optional)',
1128
- },
1129
- input: {
1130
- type: 'string',
1131
- description: 'Stdin input to send to the command (for interactive prompts). Use \\n to separate multiple answers. Example: "yes\\n\\nmy-project\\n"',
1132
- },
1133
- timeout: {
1134
- type: 'number',
1135
- description: 'Timeout in seconds (default: 120). Increase for long-running commands like installs.',
1136
- },
1137
- max_output_chars: {
1138
- type: 'number',
1139
- description: `Maximum stdout/stderr characters to return per stream (default: ${DEFAULT_COMMAND_OUTPUT_CHARS}). Large output is summarized with head/tail context.`,
1140
- },
1141
- },
1142
- required: ['command'],
1143
- },
1144
- },
1145
- {
1146
- name: 'create_directory',
1147
- description: 'Create a directory (including parent directories).',
1148
- input_schema: {
1149
- type: 'object',
1150
- properties: {
1151
- path: {
1152
- type: 'string',
1153
- description: 'Directory path to create',
1154
- },
1155
- },
1156
- required: ['path'],
1157
- },
1158
- },
1159
- {
1160
- name: 'delete_file',
1161
- description: 'Delete a file or directory. USE WITH CAUTION.',
1162
- input_schema: {
1163
- type: 'object',
1164
- properties: {
1165
- path: {
1166
- type: 'string',
1167
- description: 'Path to delete',
1168
- },
1169
- },
1170
- required: ['path'],
1171
- },
1172
- },
1173
- {
1174
- name: 'move_file',
1175
- description: 'Move or rename a file.',
1176
- input_schema: {
1177
- type: 'object',
1178
- properties: {
1179
- source: {
1180
- type: 'string',
1181
- description: 'Source path',
1182
- },
1183
- destination: {
1184
- type: 'string',
1185
- description: 'Destination path',
1186
- },
1187
- },
1188
- required: ['source', 'destination'],
1189
- },
1190
- },
1191
- {
1192
- name: 'get_context',
1193
- description: 'Get intelligent context about files including related files (imports, tests, configs). Use this to understand project structure.',
1194
- input_schema: {
1195
- type: 'object',
1196
- properties: {
1197
- files: {
1198
- type: 'array',
1199
- items: { type: 'string' },
1200
- description: 'Primary files to get context for',
1201
- },
1202
- },
1203
- required: ['files'],
1204
- },
1205
- },
1206
- {
1207
- name: 'revert_file',
1208
- description: 'Revert a file to a previous backup. Backups are created automatically on edits.',
1209
- input_schema: {
1210
- type: 'object',
1211
- properties: {
1212
- path: {
1213
- type: 'string',
1214
- description: 'File path to revert',
1215
- },
1216
- backup_index: {
1217
- type: 'number',
1218
- description: 'Backup index (0 = most recent, default: 0)',
1219
- },
1220
- },
1221
- required: ['path'],
1222
- },
1223
- },
1224
- {
1225
- name: 'run_tests',
1226
- description: 'Run project tests. Automatically detects test runner (Vitest, Jest, Mocha, pytest, Go test, etc.) and package manager (pnpm > bun > npm). Use this to validate changes and fix failing tests.',
1227
- input_schema: {
1228
- type: 'object',
1229
- properties: {
1230
- command: {
1231
- type: 'string',
1232
- description: 'Optional: Custom test command to run instead of auto-detected command',
1233
- },
1234
- cwd: {
1235
- type: 'string',
1236
- description: 'Optional: Working directory to run tests in',
1237
- },
1238
- },
1239
- },
1240
- },
1241
- {
1242
- name: 'get_test_status',
1243
- description: 'Get the status of the last test run, including pass/fail counts and failure details.',
1244
- input_schema: {
1245
- type: 'object',
1246
- properties: {},
1247
- },
1248
- },
1249
- {
1250
- name: 'get_git_status',
1251
- description: 'Get current git repository status including branch, staged/unstaged files, and clean/dirty state.',
1252
- input_schema: {
1253
- type: 'object',
1254
- properties: {},
1255
- },
1256
- },
1257
- {
1258
- name: 'get_git_diff_summary',
1259
- description: 'Get a summary of changes with line counts (insertions/deletions) per file.',
1260
- input_schema: {
1261
- type: 'object',
1262
- properties: {
1263
- target: {
1264
- type: 'string',
1265
- description: 'Optional: Target to compare against (default: HEAD). Examples: "HEAD", "main", "origin/main"',
1266
- },
1267
- },
1268
- },
1269
- },
1270
- {
1271
- name: 'get_git_changed_files',
1272
- description: 'Get list of files that have been changed (staged + unstaged). Useful for focusing edits on relevant files.',
1273
- input_schema: {
1274
- type: 'object',
1275
- properties: {
1276
- target: {
1277
- type: 'string',
1278
- description: 'Optional: Target to compare against. If not provided, returns currently changed files.',
1279
- },
1280
- },
1281
- },
1282
- },
1283
- {
1284
- name: 'git_commit',
1285
- description: 'Commit staged changes with optional AI attribution trailer (X-AI-Agent). Use this instead of run_command("git commit") to ensure proper credit.',
1286
- input_schema: {
1287
- type: 'object',
1288
- properties: {
1289
- message: { type: 'string', description: 'Commit message' },
1290
- agent_name: { type: 'string', description: 'Name of the AI agent/persona (e.g. "Arya", "Coder")' }
1291
- },
1292
- required: ['message']
1293
- }
1294
- },
1295
- {
1296
- name: 'git_blame_ai',
1297
- description: 'Get git blame output with AI attribution. Shows who (Human or AI Agent) wrote each line.',
1298
- input_schema: {
1299
- type: 'object',
1300
- properties: {
1301
- file_path: { type: 'string', description: 'Path to file' }
1302
- },
1303
- required: ['file_path']
1304
- }
1305
- },
1306
- {
1307
- name: 'create_git_checkpoint',
1308
- description: 'Create a safe restore point before making risky changes. Can use git stash or commit strategy.',
1309
- input_schema: {
1310
- type: 'object',
1311
- properties: {
1312
- message: {
1313
- type: 'string',
1314
- description: 'Description of the checkpoint (e.g., "before refactoring auth module")',
1315
- },
1316
- strategy: {
1317
- type: 'string',
1318
- enum: ['stash', 'commit'],
1319
- description: 'Checkpoint strategy: "stash" (default) or "commit"',
1320
- },
1321
- },
1322
- required: ['message'],
1323
- },
1324
- },
1325
- {
1326
- name: 'revert_to_git_checkpoint',
1327
- description: 'Revert code to a previous checkpoint. REQUIRES explicit confirmation. Use list_checkpoints to see available checkpoints.',
1328
- input_schema: {
1329
- type: 'object',
1330
- properties: {
1331
- checkpoint_id: {
1332
- type: 'string',
1333
- description: 'Checkpoint ID (e.g., "stash@{0}" or commit hash)',
1334
- },
1335
- checkpoint_type: {
1336
- type: 'string',
1337
- enum: ['stash', 'commit'],
1338
- description: 'Type of checkpoint',
1339
- },
1340
- confirm: {
1341
- type: 'boolean',
1342
- description: 'Must be set to true to confirm the revert operation',
1343
- },
1344
- },
1345
- required: ['checkpoint_id', 'checkpoint_type', 'confirm'],
1346
- },
1347
- },
1348
- {
1349
- name: 'git_show_diff',
1350
- description: 'Get unified diff output for a file or entire repository. Useful for reviewing changes.',
1351
- input_schema: {
1352
- type: 'object',
1353
- properties: {
1354
- file_path: {
1355
- type: 'string',
1356
- description: 'Optional: Specific file to get diff for. If not provided, shows all changes.',
1357
- },
1358
- target: {
1359
- type: 'string',
1360
- description: 'Optional: Target to compare against (default: HEAD)',
1361
- },
1362
- },
1363
- },
1364
- },
1365
- {
1366
- name: 'get_mcp_status',
1367
- description: 'Get status of MCP (Model Context Protocol) servers. Shows which servers are configured, connected, and what tools/resources/prompts are available. Use this to check what MCP capabilities you have access to.',
1368
- input_schema: {
1369
- type: 'object',
1370
- properties: {},
1371
- },
1372
- },
1373
- {
1374
- name: 'mcp_list_resources',
1375
- description: 'List resources exposed by connected MCP servers.',
1376
- input_schema: { type: 'object', properties: {} },
1377
- },
1378
- {
1379
- name: 'mcp_read_resource',
1380
- description: 'Read an MCP resource by uri. Use uri in server::uri form.',
1381
- input_schema: {
1382
- type: 'object',
1383
- properties: { uri: { type: 'string', description: 'Resource URI in server::uri form' } },
1384
- required: ['uri'],
1385
- },
1386
- },
1387
- {
1388
- name: 'mcp_list_prompts',
1389
- description: 'List prompts exposed by connected MCP servers.',
1390
- input_schema: { type: 'object', properties: {} },
1391
- },
1392
- {
1393
- name: 'mcp_get_prompt',
1394
- description: 'Fetch an MCP prompt template by name (server::promptName) with optional args.',
1395
- input_schema: {
1396
- type: 'object',
1397
- properties: {
1398
- name: { type: 'string', description: 'Prompt name in server::promptName form' },
1399
- args: { type: 'object', description: 'Optional prompt arguments' },
1400
- },
1401
- required: ['name'],
1402
- },
1403
- },
1404
- {
1405
- name: 'mcp_auth',
1406
- description: 'Authenticate an MCP server using OAuth. Use action=start to get an authUrl; if localhost callback is not reachable, use action=finish with callback_url pasted from the browser.',
1407
- input_schema: {
1408
- type: 'object',
1409
- properties: {
1410
- server: { type: 'string', description: 'MCP server name (as configured)' },
1411
- action: { type: 'string', enum: ['start', 'finish'], description: 'OAuth flow action' },
1412
- callback_url: { type: 'string', description: 'Full callback URL (only for action=finish)' },
1413
- },
1414
- required: ['server'],
1415
- },
1416
- },
1417
- {
1418
- name: 'resolve_merge_conflicts',
1419
- description: 'Scan for git merge conflicts and get details for resolution. Returns the conflict blocks (ours/theirs) so the agent can fix them.',
1420
- input_schema: {
1421
- type: 'object',
1422
- properties: {
1423
- file_path: { type: 'string', description: 'Optional: Specific file to resolve. If omitted, picks the first conflicting file.' }
1424
- }
1425
- }
1426
- },
1427
- {
1428
- name: 'run_swarm',
1429
- description: 'Run multiple specialized sub-agents in parallel (separate background processes) to save wall-clock time. Each entry has worker_type + task. Cap concurrent workers with max_parallel (default 6). Risk: workers editing the same files can conflict—split work by disjoint paths or use delegate_subtask serially when unsure.',
1430
- input_schema: {
1431
- type: 'object',
1432
- properties: {
1433
- subtasks: {
1434
- type: 'array',
1435
- description: 'One object per worker; each runs as an isolated sub-agent.',
1436
- items: {
1437
- type: 'object',
1438
- properties: {
1439
- task: { type: 'string', description: 'Task for this worker only.' },
1440
- worker_type: {
1441
- type: 'string',
1442
- description: 'Agent mode for this worker',
1443
- enum: ['agent', 'plan', 'review']
1444
- }
1445
- },
1446
- required: ['task', 'worker_type']
1447
- },
1448
- minItems: 1
1449
- },
1450
- timeout_ms: {
1451
- type: 'number',
1452
- description: 'Optional per-subtask timeout in ms (same as delegate_subtask; default five minutes).'
1453
- },
1454
- max_parallel: {
1455
- type: 'number',
1456
- description: 'Max concurrent background agents (default 6). Lower on small machines; raise only if subtasks are independent.'
1457
- }
1458
- },
1459
- required: ['subtasks']
1460
- }
1461
- },
1462
- {
1463
- name: 'grep_code',
1464
- description: 'Search for a text pattern across your codebase using ripgrep (or grep fallback). Returns matching file paths, line numbers, and line content. Use this to find function usages, variable references, imports, error messages, etc. Much faster than reading files one by one.',
1465
- input_schema: {
1466
- type: 'object',
1467
- properties: {
1468
- pattern: {
1469
- type: 'string',
1470
- description: 'Text or regex pattern to search for',
1471
- },
1472
- path: {
1473
- type: 'string',
1474
- description: 'Directory or file to search in (default: current working directory)',
1475
- },
1476
- ignore_case: {
1477
- type: 'boolean',
1478
- description: 'Case-insensitive search (default: false)',
1479
- },
1480
- file_pattern: {
1481
- type: 'string',
1482
- description: 'Glob pattern to filter files, e.g. "*.ts" or "*.py"',
1483
- },
1484
- max_results: {
1485
- type: 'number',
1486
- description: 'Maximum results to return (default: 50)',
1487
- },
1488
- },
1489
- required: ['pattern'],
1490
- },
1491
- },
1492
- {
1493
- name: 'web_search',
1494
- description: 'Search the web using DuckDuckGo. Returns titles, URLs, and snippets. Use this to look up documentation, find solutions to errors, research libraries, or get up-to-date information. No API key required.',
1495
- input_schema: {
1496
- type: 'object',
1497
- properties: {
1498
- query: {
1499
- type: 'string',
1500
- description: 'Search query',
1501
- },
1502
- max_results: {
1503
- type: 'number',
1504
- description: 'Max results to return (default: 8)',
1505
- },
1506
- },
1507
- required: ['query'],
1508
- },
1509
- },
1510
- {
1511
- name: 'remember_lesson',
1512
- description: 'Save a key lesson, fix, or optimization to persistent memory. Use this when you solve a tricky problem, fix a build error, or find a better way to do something. This helps you avoid repeating mistakes in the future.',
1513
- input_schema: {
1514
- type: 'object',
1515
- properties: {
1516
- trigger: { type: 'string', description: 'The situation, error, or context that triggered this learning (e.g. "Build failed with error X")' },
1517
- action: { type: 'string', description: 'What you did to fix or improve it' },
1518
- outcome: { type: 'string', description: 'The positive result' },
1519
- tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags for categorization' }
1520
- },
1521
- required: ['trigger', 'action', 'outcome']
1522
- }
1523
- },
1524
- {
1525
- name: 'synthesize_tool',
1526
- description: 'Register a new session-scoped tool (meta-agent). Use when you need a reusable script for repeated operations or after repeated failures. The script runs in the same sandbox as run_command. Name must be lowercase with underscores (e.g. my_helper).',
1527
- input_schema: {
1528
- type: 'object',
1529
- properties: {
1530
- name: { type: 'string', description: 'Tool name (lowercase, letters/numbers/underscores only)' },
1531
- description: { type: 'string', description: 'Short description of what the tool does' },
1532
- script: { type: 'string', description: 'Shell command or script to run when the tool is invoked (e.g. "grep -r pattern src/")' }
1533
- },
1534
- required: ['name', 'script']
1535
- }
1536
- },
1537
- {
1538
- name: 'take_screenshot',
1539
- description: 'Disabled: no bundled browser. Returns guidance; use run_command with agent-browser screenshot or a browser MCP for captures.',
1540
- input_schema: {
1541
- type: 'object',
1542
- properties: {
1543
- url: { type: 'string', description: 'URL to visit (e.g., http://localhost:3000)' },
1544
- path: { type: 'string', description: 'Output path for the screenshot (e.g., screenshot.png)' },
1545
- fullPage: { type: 'boolean', description: 'Capture full page? Default: true' }
1546
- },
1547
- required: ['url', 'path']
1548
- }
1549
- },
1550
- {
1551
- name: 'get_console_logs',
1552
- description: 'Disabled: no bundled browser. Returns guidance; use agent-browser or host browser tooling for console capture.',
1553
- input_schema: {
1554
- type: 'object',
1555
- properties: {
1556
- url: { type: 'string', description: 'URL to visit' }
1557
- },
1558
- required: ['url']
1559
- }
1560
- },
1561
- {
1562
- name: 'run_visual_test',
1563
- description: 'Disabled: no bundled browser. Returns guidance; use external visual regression or agent-browser workflows in the target project.',
1564
- input_schema: {
1565
- type: 'object',
1566
- properties: {
1567
- url: { type: 'string', description: 'URL to test (e.g., http://localhost:3000)' },
1568
- baseline_path: { type: 'string', description: 'Path to baseline screenshot file (e.g., baselines/homepage.png)' },
1569
- output_dir: { type: 'string', description: 'Directory for test output (unused; tool disabled)' }
1570
- },
1571
- required: ['url', 'baseline_path']
1572
- }
1573
- },
1574
- {
1575
- name: 'check_accessibility',
1576
- description: 'Disabled: no bundled browser. Returns guidance; use Lighthouse, axe, or agent-browser in the environment.',
1577
- input_schema: {
1578
- type: 'object',
1579
- properties: {
1580
- url: { type: 'string', description: 'URL to audit (e.g., http://localhost:3000)' }
1581
- },
1582
- required: ['url']
1583
- }
1584
- },
1585
- {
1586
- name: 'measure_performance',
1587
- description: 'Disabled: no bundled browser. Returns guidance; use Lighthouse or browser DevTools via your workflow.',
1588
- input_schema: {
1589
- type: 'object',
1590
- properties: {
1591
- url: { type: 'string', description: 'URL to measure (e.g., http://localhost:3000)' }
1592
- },
1593
- required: ['url']
1594
- }
1595
- },
1596
- {
1597
- name: 'test_responsive',
1598
- description: 'Disabled: no bundled browser. Returns guidance; use agent-browser or project E2E tooling for viewport checks.',
1599
- input_schema: {
1600
- type: 'object',
1601
- properties: {
1602
- url: { type: 'string', description: 'URL to test (e.g., http://localhost:3000)' },
1603
- output_dir: { type: 'string', description: 'Directory for screenshots (default: .responsive-screenshots)' },
1604
- viewports: {
1605
- type: 'array',
1606
- items: {
1607
- type: 'object',
1608
- properties: {
1609
- name: { type: 'string' },
1610
- width: { type: 'number' },
1611
- height: { type: 'number' }
1612
- }
1613
- },
1614
- description: 'Custom viewports. Default: mobile (375x667), tablet (768x1024), desktop (1280x800), desktop-large (1920x1080)'
1615
- }
1616
- },
1617
- required: ['url']
1618
- }
1619
- },
1620
- {
1621
- name: 'capture_network',
1622
- description: 'Disabled: no bundled browser. Returns guidance; use browser DevTools HAR, agent-browser, or MCP browser network logs.',
1623
- input_schema: {
1624
- type: 'object',
1625
- properties: {
1626
- url: { type: 'string', description: 'URL to load and monitor (e.g., http://localhost:3000)' }
1627
- },
1628
- required: ['url']
1629
- }
1630
- },
1631
- {
1632
- name: 'fetch_url',
1633
- description: 'Fetch and read content from any URL. HTML is automatically stripped to plain text. Use this to read documentation pages, API references, blog posts, or any web content. Supports HTML, JSON, and plain text.',
1634
- input_schema: {
1635
- type: 'object',
1636
- properties: {
1637
- url: {
1638
- type: 'string',
1639
- description: 'URL to fetch',
1640
- },
1641
- max_length: {
1642
- type: 'number',
1643
- description: 'Max characters to return (default: 20000). Increase for long docs.',
1644
- },
1645
- },
1646
- required: ['url'],
1647
- },
1648
- },
1649
- {
1650
- name: 'update_memory',
1651
- description: 'Save important project knowledge to .xibecode/memory.md so it persists across sessions. Use this to remember: coding conventions, architecture decisions, frequently used commands, project-specific notes, or anything useful for future sessions. The memory file is automatically loaded at the start of each session.',
1652
- input_schema: {
1653
- type: 'object',
1654
- properties: {
1655
- content: {
1656
- type: 'string',
1657
- description: 'Content to save to memory (markdown format recommended)',
1658
- },
1659
- append: {
1660
- type: 'boolean',
1661
- description: 'If true (default), append to existing memory. If false, replace entire memory.',
1662
- },
1663
- },
1664
- required: ['content'],
1665
- },
1666
- },
1667
- {
1668
- name: 'search_skills_sh',
1669
- description: 'Search for AI coding skills from the skills.sh marketplace. Returns a list of available skills with their IDs and descriptions.',
1670
- input_schema: {
1671
- type: 'object',
1672
- properties: {
1673
- query: {
1674
- type: 'string',
1675
- description: 'Search query (e.g., "react", "python", "testing")',
1676
- },
1677
- },
1678
- required: ['query'],
1679
- },
1680
- },
1681
- {
1682
- name: 'install_skill_from_skills_sh',
1683
- description: 'Install an AI coding skill from skills.sh using its ID found via search_skills_sh. The skill will be downloaded and available for use.',
1684
- input_schema: {
1685
- type: 'object',
1686
- properties: {
1687
- skill_id: {
1688
- type: 'string',
1689
- description: 'The unique ID of the skill to install (e.g., "vercel-labs/agent-skills@vercel-react-best-practices")',
1690
- },
1691
- },
1692
- required: ['skill_id'],
1693
- },
1694
- },
1695
- // AI Test Generation Tools
1696
- {
1697
- name: 'generate_tests',
1698
- description: 'AI-powered test generation. Analyzes a source file and automatically generates comprehensive test cases including unit tests, edge cases, error handling tests, and type checks. Supports Vitest, Jest, Mocha, pytest, and Go test frameworks.',
1699
- input_schema: {
1700
- type: 'object',
1701
- properties: {
1702
- file_path: {
1703
- type: 'string',
1704
- description: 'Path to the source file to generate tests for (e.g., "src/utils/helpers.ts")',
1705
- },
1706
- framework: {
1707
- type: 'string',
1708
- enum: ['vitest', 'jest', 'mocha', 'pytest', 'go'],
1709
- description: 'Test framework to use. Auto-detected if not specified.',
1710
- },
1711
- output_dir: {
1712
- type: 'string',
1713
- description: 'Directory for test output. Default: __tests__ for JS/TS, tests/ for Python, same dir for Go.',
1714
- },
1715
- include_edge_cases: {
1716
- type: 'boolean',
1717
- description: 'Include edge case tests (empty strings, null values, boundary conditions). Default: true',
1718
- },
1719
- include_mocks: {
1720
- type: 'boolean',
1721
- description: 'Include mock setup code for dependencies. Default: true',
1722
- },
1723
- max_tests_per_function: {
1724
- type: 'number',
1725
- description: 'Maximum test cases per function. Default: 5',
1726
- },
1727
- write_file: {
1728
- type: 'boolean',
1729
- description: 'Write generated tests to file. Default: false (returns content only)',
1730
- },
1731
- },
1732
- required: ['file_path'],
1733
- },
1734
- },
1735
- {
1736
- name: 'analyze_code_for_tests',
1737
- description: 'Analyze a source file to understand its structure before generating tests. Returns information about functions, classes, exports, imports, complexity, and dependencies. Useful for understanding what needs to be tested.',
1738
- input_schema: {
1739
- type: 'object',
1740
- properties: {
1741
- file_path: {
1742
- type: 'string',
1743
- description: 'Path to the source file to analyze (e.g., "src/utils/helpers.ts")',
1744
- },
1745
- },
1746
- required: ['file_path'],
1747
- },
1748
- },
1749
- ];
1750
- // Merge MCP tools
1751
- const mcpTools = [];
1752
- if (this.mcpClientManager) {
1753
- const availableMCPTools = this.mcpClientManager.getAvailableTools();
1754
- for (const mcpTool of availableMCPTools) {
1755
- mcpTools.push({
1756
- name: `${mcpTool.serverName}::${mcpTool.name}`,
1757
- description: `[MCP: ${mcpTool.serverName}] ${mcpTool.description}`,
1758
- input_schema: mcpTool.inputSchema,
1759
- });
1760
- }
1761
- }
1762
- // Merge plugin tools
1763
- const pluginTools = this.pluginManager.getPluginTools();
1764
- const dynamicToolDefs = Array.from(this.dynamicTools.entries()).map(([name, def]) => ({
1765
- name,
1766
- description: `[Session tool] ${def.description}`,
1767
- input_schema: { type: 'object', properties: {} },
1768
- }));
1769
- return [...coreTools, ...dynamicToolDefs, ...mcpTools, ...pluginTools];
1770
- }
1771
- /**
1772
- * Resolve relative file path to absolute path
1773
- *
1774
- * @param filePath - Relative or absolute file path
1775
- * @returns Absolute path resolved against working directory
1776
- *
1777
- * @internal
1778
- */
1779
- resolvePath(filePath) {
1780
- const result = sanitizePath(this.workingDir, filePath);
1781
- if (!result.ok)
1782
- throw new Error(result.message);
1783
- return result.path;
1784
- }
1785
- /**
1786
- * Read file contents
1787
- *
1788
- * Reads a file from the filesystem. Supports partial reading by line range
1789
- * to avoid token limits for large files. Always returns UTF-8 encoded text.
1790
- *
1791
- * @example
1792
- * ```typescript
1793
- * // Read entire file
1794
- * const result = await executor.execute('read_file', {
1795
- * path: 'src/app.ts'
1796
- * });
1797
- *
1798
- * // Read specific line range
1799
- * const partial = await executor.execute('read_file', {
1800
- * path: 'src/large-file.ts',
1801
- * start_line: 100,
1802
- * end_line: 200
1803
- * });
1804
- * ```
1805
- *
1806
- * @param filePath - Path to file (relative to working directory)
1807
- * @param startLine - Optional start line for partial read (1-indexed)
1808
- * @param endLine - Optional end line for partial read (inclusive)
1809
- * @returns Object with path, content, and line info
1810
- *
1811
- * @throws {FileNotFoundError} If file doesn't exist
1812
- * @throws {PermissionError} If file is not readable
1813
- *
1814
- * @category File Operations
1815
- * @mode All modes
1816
- * @since 0.1.0
1817
- */
1818
- async readFile(filePath, startLine, endLine) {
1819
- const fullPath = this.resolvePath(filePath);
1820
- try {
1821
- const content = await fs.readFile(fullPath, 'utf-8');
1822
- if (startLine !== undefined && endLine !== undefined) {
1823
- const lines = content.split('\n');
1824
- const chunk = lines.slice(startLine - 1, endLine).join('\n');
1825
- return {
1826
- path: filePath,
1827
- content: chunk,
1828
- lines: endLine - startLine + 1,
1829
- total_lines: lines.length,
1830
- partial: true,
1831
- };
1832
- }
1833
- const lines = content.split('\n');
1834
- return {
1835
- path: filePath,
1836
- content,
1837
- lines: lines.length,
1838
- size: content.length,
1839
- };
1840
- }
1841
- catch (error) {
1842
- return { error: true, success: false, message: `Failed to read ${filePath}: ${error.message}` };
1843
- }
1844
- }
1845
- async readMultipleFiles(paths) {
1846
- const CONCURRENCY_LIMIT = 20;
1847
- const results = [];
1848
- for (let i = 0; i < paths.length; i += CONCURRENCY_LIMIT) {
1849
- const chunk = paths.slice(i, i + CONCURRENCY_LIMIT);
1850
- const chunkResults = await Promise.allSettled(chunk.map(async (p) => {
1851
- const content = await this.readFile(p);
1852
- return { path: p, ...content };
1853
- }));
1854
- results.push(...chunkResults);
1855
- }
1856
- return {
1857
- files: results
1858
- .filter((r) => r.status === 'fulfilled')
1859
- .map(r => r.value),
1860
- errors: results
1861
- .filter((r) => r.status === 'rejected')
1862
- .map((r, i) => ({ path: paths[i], error: r.reason.message })),
1863
- };
1864
- }
1865
- /**
1866
- * Write content to file
1867
- *
1868
- * Creates or overwrites a file with the given content. Automatically creates
1869
- * parent directories if they don't exist. Creates a backup before overwriting
1870
- * existing files.
1871
- *
1872
- * In dry-run mode, shows what would be written without making changes.
1873
- *
1874
- * @example
1875
- * ```typescript
1876
- * await executor.execute('write_file', {
1877
- * path: 'src/new-file.ts',
1878
- * content: 'export const hello = "world";'
1879
- * });
1880
- * ```
1881
- *
1882
- * @param filePath - Path to file (relative to working directory)
1883
- * @param content - File content to write
1884
- * @returns Object with success status, path, and file info
1885
- *
1886
- * @throws {PermissionError} If directory is not writable
1887
- *
1888
- * @category File Operations
1889
- * @mode Write modes (agent, engineer, architect)
1890
- * @since 0.1.0
1891
- */
1892
- async writeFile(filePath, content) {
1893
- const fullPath = this.resolvePath(filePath);
1894
- if (this.dryRun) {
1895
- const lines = content.split('\n').length;
1896
- return {
1897
- success: true,
1898
- dryRun: true,
1899
- path: filePath,
1900
- lines,
1901
- size: content.length,
1902
- message: `[DRY RUN] Would write ${lines} lines to ${filePath}`,
1903
- };
1904
- }
1905
- try {
1906
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
1907
- await fs.writeFile(fullPath, content, 'utf-8');
1908
- const lines = content.split('\n').length;
1909
- return {
1910
- success: true,
1911
- path: filePath,
1912
- lines,
1913
- size: content.length,
1914
- };
1915
- }
1916
- catch (error) {
1917
- return { error: true, success: false, message: `Failed to write ${filePath}: ${error.message}` };
1918
- }
1919
- }
1920
- /**
1921
- * Edit file using search and replace
1922
- *
1923
- * Performs intelligent search-and-replace editing using the FileEditor's
1924
- * smart edit strategy. Searches for exact string matches and replaces them.
1925
- * Automatically handles multi-line strings and special characters.
1926
- *
1927
- * Creates a backup before editing. In dry-run mode, shows what would be
1928
- * changed without modifying the file.
1929
- *
1930
- * @example
1931
- * ```typescript
1932
- * // Replace first occurrence
1933
- * await executor.execute('edit_file', {
1934
- * path: 'src/app.ts',
1935
- * search: 'const oldValue = 1;',
1936
- * replace: 'const newValue = 2;'
1937
- * });
1938
- *
1939
- * // Replace all occurrences
1940
- * await executor.execute('edit_file', {
1941
- * path: 'src/app.ts',
1942
- * search: 'oldName',
1943
- * replace: 'newName',
1944
- * all: true
1945
- * });
1946
- * ```
1947
- *
1948
- * @param filePath - Path to file (relative to working directory)
1949
- * @param search - Exact string to search for (can be multi-line)
1950
- * @param replace - Replacement string
1951
- * @param all - Replace all occurrences (default: false, replaces first only)
1952
- * @returns Object with success status, changes made, and diff
1953
- *
1954
- * @throws {FileNotFoundError} If file doesn't exist
1955
- * @throws {SearchNotFoundError} If search string not found
1956
- *
1957
- * @category File Operations
1958
- * @mode Write modes (agent, engineer, architect)
1959
- * @since 0.1.0
1960
- */
1961
- async editFile(filePath, search, replace, all) {
1962
- if (this.dryRun) {
1963
- return {
1964
- success: true,
1965
- dryRun: true,
1966
- path: filePath,
1967
- message: `[DRY RUN] Would replace "${search.slice(0, 50)}..." with "${replace.slice(0, 50)}..."`,
1968
- };
1969
- }
1970
- const result = await this.fileEditor.smartEdit(filePath, { search, replace, all });
1971
- return result;
1972
- }
1973
- async editLines(filePath, startLine, endLine, newContent) {
1974
- if (this.dryRun) {
1975
- const lines = newContent.split('\n').length;
1976
- return {
1977
- success: true,
1978
- dryRun: true,
1979
- path: filePath,
1980
- message: `[DRY RUN] Would replace lines ${startLine}-${endLine} with ${lines} new lines`,
1981
- };
1982
- }
1983
- const result = await this.fileEditor.editLineRange(filePath, { startLine, endLine, newContent });
1984
- return result;
1985
- }
1986
- async verifiedEditFile(filePath, startLine, endLine, oldContent, newContent) {
1987
- if (this.dryRun) {
1988
- return {
1989
- success: true,
1990
- dryRun: true,
1991
- path: filePath,
1992
- message: `[DRY RUN] Would verified-edit lines ${startLine}-${endLine} in ${filePath}`,
1993
- };
1994
- }
1995
- const result = await this.fileEditor.verifiedEdit(filePath, { startLine, endLine, oldContent, newContent });
1996
- return result;
1997
- }
1998
- async insertAtLine(filePath, line, content) {
1999
- if (this.dryRun) {
2000
- const lines = content.split('\n').length;
2001
- return {
2002
- success: true,
2003
- dryRun: true,
2004
- path: filePath,
2005
- message: `[DRY RUN] Would insert ${lines} lines at line ${line}`,
2006
- };
2007
- }
2008
- const result = await this.fileEditor.insertAtLine(filePath, line, content);
2009
- return result;
2010
- }
2011
- async listDirectory(dirPath) {
2012
- const fullPath = this.resolvePath(dirPath);
2013
- try {
2014
- const entries = await fs.readdir(fullPath, { withFileTypes: true });
2015
- const CONCURRENCY_LIMIT = 50;
2016
- const results = [];
2017
- for (let i = 0; i < entries.length; i += CONCURRENCY_LIMIT) {
2018
- const chunk = entries.slice(i, i + CONCURRENCY_LIMIT);
2019
- const chunkResults = await Promise.all(chunk.map(async (entry) => {
2020
- const entryPath = path.join(fullPath, entry.name);
2021
- try {
2022
- const stats = await fs.stat(entryPath);
2023
- return {
2024
- name: entry.name,
2025
- type: entry.isDirectory() ? 'directory' : 'file',
2026
- size: stats.size,
2027
- modified: stats.mtime,
2028
- };
2029
- }
2030
- catch {
2031
- return {
2032
- name: entry.name,
2033
- type: entry.isDirectory() ? 'directory' : 'file',
2034
- size: 0,
2035
- };
2036
- }
2037
- }));
2038
- results.push(...chunkResults);
2039
- }
2040
- return { path: dirPath, entries: results, count: results.length };
2041
- }
2042
- catch (error) {
2043
- return { error: true, success: false, message: `Failed to list directory ${dirPath}: ${error.message}` };
2044
- }
2045
- }
2046
- async searchFiles(pattern, searchPath = '.') {
2047
- try {
2048
- const files = await this.contextManager.searchFiles(pattern, { maxResults: 100 });
2049
- return {
2050
- pattern,
2051
- files,
2052
- count: files.length,
2053
- };
2054
- }
2055
- catch (error) {
2056
- return { error: true, success: false, message: `Failed to search files: ${error.message}` };
2057
- }
2058
- }
2059
- /**
2060
- * Execute a shell command
2061
- *
2062
- * Runs a command in a shell (bash on Unix, PowerShell on Windows).
2063
- * Captures stdout and stderr. Supports stdin input for interactive commands.
2064
- * Automatically times out after 120 seconds (configurable).
2065
- *
2066
- * Safety checks are performed before execution to block dangerous commands
2067
- * like `rm -rf /`, malicious scripts, and other high-risk operations.
2068
- *
2069
- * @example
2070
- * ```typescript
2071
- * // Run a simple command
2072
- * const result = await executor.execute('run_command', {
2073
- * command: 'npm test'
2074
- * });
2075
- *
2076
- * // Run with specific working directory
2077
- * await executor.execute('run_command', {
2078
- * command: 'ls -la',
2079
- * cwd: './src'
2080
- * });
2081
- *
2082
- * // Run with stdin input
2083
- * await executor.execute('run_command', {
2084
- * command: 'cat > output.txt',
2085
- * input: 'Hello World'
2086
- * });
2087
- *
2088
- * // Run with custom timeout
2089
- * await executor.execute('run_command', {
2090
- * command: 'npm install',
2091
- * timeout: 300 // 5 minutes
2092
- * });
2093
- * ```
2094
- *
2095
- * @param command - Shell command to execute
2096
- * @param cwd - Working directory (default: executor's working directory)
2097
- * @param input - Optional stdin input for interactive commands
2098
- * @param timeout - Timeout in seconds (default: 120)
2099
- * @returns Object with stdout, stderr, exit code, and execution time
2100
- *
2101
- * @throws {SafetyError} If command is blocked by safety checker
2102
- * @throws {TimeoutError} If command exceeds timeout
2103
- *
2104
- * @category Shell Commands
2105
- * @mode Command modes (agent, engineer, debugger, tester)
2106
- * @risk-level High
2107
- * @since 0.1.0
2108
- */
2109
- async runDynamicTool(toolName, _input) {
2110
- const def = this.dynamicTools.get(toolName);
2111
- if (!def)
2112
- return { error: true, success: false, message: `Dynamic tool "${toolName}" not found` };
2113
- return this.runCommand(def.script, this.workingDir, undefined, 60);
2114
- }
2115
- async runCommand(command, cwd, input, timeout, maxOutputChars) {
2116
- const workDir = cwd ? this.resolvePath(cwd) : this.workingDir;
2117
- const timeoutMs = (timeout || 120) * 1000;
2118
- const shell = this.platform === 'win32' ? 'powershell.exe' : '/bin/sh';
2119
- const outputLimit = Math.max(1000, maxOutputChars ?? DEFAULT_COMMAND_OUTPUT_CHARS);
2120
- return new Promise((resolve) => {
2121
- const child = spawn(shell, this.platform === 'win32' ? ['-Command', command] : ['-c', command], {
2122
- cwd: workDir,
2123
- env: { ...process.env },
2124
- stdio: ['pipe', 'pipe', 'pipe'],
2125
- });
2126
- let stdout = '';
2127
- let stderr = '';
2128
- let timedOut = false;
2129
- let settled = false;
2130
- const finish = (result) => {
2131
- if (settled)
2132
- return;
2133
- settled = true;
2134
- clearTimeout(timer);
2135
- const compactedStdout = compactCommandOutput(String(result.stdout ?? '').trim(), outputLimit);
2136
- const compactedStderr = compactCommandOutput(String(result.stderr ?? '').trim(), outputLimit);
2137
- resolve({
2138
- ...result,
2139
- stdout: compactedStdout.output,
2140
- stderr: compactedStderr.output,
2141
- truncated: compactedStdout.truncated || compactedStderr.truncated,
2142
- originalStdoutLength: compactedStdout.originalLength,
2143
- originalStderrLength: compactedStderr.originalLength,
2144
- platform: this.platform,
2145
- });
2146
- };
2147
- const timer = setTimeout(() => {
2148
- timedOut = true;
2149
- child.kill('SIGTERM');
2150
- }, timeoutMs);
2151
- child.stdout.on('data', (data) => { stdout += data.toString(); });
2152
- child.stderr.on('data', (data) => { stderr += data.toString(); });
2153
- if (input) {
2154
- child.stdin.write(input.replace(/\\n/g, '\n'));
2155
- }
2156
- child.stdin.end();
2157
- child.on('close', (code) => {
2158
- finish({
2159
- stdout,
2160
- stderr: timedOut
2161
- ? `${stderr}\nCommand timed out after ${timeout || 120}s. Try increasing the timeout parameter, or use non-interactive flags like --yes to avoid prompts.`
2162
- : stderr,
2163
- success: !timedOut && code === 0,
2164
- exitCode: code,
2165
- timedOut,
2166
- });
2167
- });
2168
- child.on('error', (err) => {
2169
- finish({
2170
- stdout,
2171
- stderr: err.message,
2172
- success: false,
2173
- exitCode: undefined,
2174
- timedOut,
2175
- });
2176
- });
2177
- });
2178
- }
2179
- async createDirectory(dirPath) {
2180
- const fullPath = this.resolvePath(dirPath);
2181
- try {
2182
- await fs.mkdir(fullPath, { recursive: true });
2183
- return { success: true, path: dirPath };
2184
- }
2185
- catch (error) {
2186
- return { error: true, success: false, message: `Failed to create directory ${dirPath}: ${error.message}` };
2187
- }
2188
- }
2189
- async deleteFile(filePath) {
2190
- const fullPath = this.resolvePath(filePath);
2191
- if (this.dryRun) {
2192
- try {
2193
- const stats = await fs.stat(fullPath);
2194
- const type = stats.isDirectory() ? 'directory' : 'file';
2195
- return {
2196
- success: true,
2197
- dryRun: true,
2198
- path: filePath,
2199
- message: `[DRY RUN] Would delete ${type}: ${filePath}`,
2200
- };
2201
- }
2202
- catch (error) {
2203
- return {
2204
- success: true,
2205
- dryRun: true,
2206
- path: filePath,
2207
- message: `[DRY RUN] Would attempt to delete ${filePath} (file not found)`,
2208
- };
2209
- }
2210
- }
2211
- try {
2212
- const stats = await fs.stat(fullPath);
2213
- if (stats.isDirectory()) {
2214
- await fs.rm(fullPath, { recursive: true, force: true });
2215
- }
2216
- else {
2217
- await fs.unlink(fullPath);
2218
- }
2219
- return { success: true, path: filePath };
2220
- }
2221
- catch (error) {
2222
- return { error: true, success: false, message: `Failed to delete ${filePath}: ${error.message}` };
2223
- }
2224
- }
2225
- async moveFile(source, destination) {
2226
- if (this.dryRun) {
2227
- return {
2228
- success: true,
2229
- dryRun: true,
2230
- source,
2231
- destination,
2232
- message: `[DRY RUN] Would move ${source} to ${destination}`,
2233
- };
2234
- }
2235
- const sourcePath = this.resolvePath(source);
2236
- const destPath = this.resolvePath(destination);
2237
- try {
2238
- await fs.rename(sourcePath, destPath);
2239
- return { success: true, source, destination };
2240
- }
2241
- catch (error) {
2242
- return { error: true, success: false, message: `Failed to move ${source}: ${error.message}` };
2243
- }
2244
- }
2245
- async getContext(files) {
2246
- try {
2247
- const context = await this.contextManager.buildContext(files);
2248
- return {
2249
- files: context.files.map(f => ({
2250
- path: f.path,
2251
- lines: f.lines,
2252
- language: f.language,
2253
- size: f.size,
2254
- })),
2255
- totalFiles: context.files.length,
2256
- estimatedTokens: context.totalTokens,
2257
- };
2258
- }
2259
- catch (error) {
2260
- return { error: true, success: false, message: `Failed to get context: ${error.message}` };
2261
- }
2262
- }
2263
- async revertFile(filePath, backupIndex = 0) {
2264
- const result = await this.fileEditor.revertToBackup(filePath, backupIndex);
2265
- return result;
2266
- }
2267
- // ── Test Runner Methods ──
2268
- lastTestResult = null;
2269
- async runTests(customCommand, cwd) {
2270
- try {
2271
- // Detect test runner and command
2272
- const testInfo = await this.testRunner.detectTestRunner(customCommand || this.testCommandOverride);
2273
- if (!testInfo.detected || !testInfo.command) {
2274
- return {
2275
- error: true,
2276
- success: false,
2277
- message: 'No test runner detected. Ensure package.json has a test script or specify a custom command.',
2278
- };
2279
- }
2280
- const workDir = cwd ? this.resolvePath(cwd) : this.workingDir;
2281
- const startTime = Date.now();
2282
- // Run the test command
2283
- const result = await this.runCommand(testInfo.command, cwd, undefined, 300);
2284
- const duration = Date.now() - startTime;
2285
- // Parse test output
2286
- const parsed = this.testRunner.parseTestOutput(result.stdout + '\n' + result.stderr, testInfo.runner);
2287
- const failures = result.success
2288
- ? []
2289
- : this.testRunner.extractFailures(result.stdout + '\n' + result.stderr);
2290
- this.lastTestResult = {
2291
- success: result.success,
2292
- exitCode: result.exitCode,
2293
- output: result.stdout,
2294
- errors: result.stderr,
2295
- duration,
2296
- runner: testInfo.runner,
2297
- command: testInfo.command,
2298
- packageManager: testInfo.packageManager,
2299
- ...parsed,
2300
- failures,
2301
- };
2302
- return {
2303
- success: result.success,
2304
- runner: testInfo.runner,
2305
- command: testInfo.command,
2306
- packageManager: testInfo.packageManager,
2307
- duration,
2308
- testsRun: parsed.testsRun,
2309
- testsPassed: parsed.testsPassed,
2310
- testsFailed: parsed.testsFailed,
2311
- exitCode: result.exitCode,
2312
- output: result.stdout,
2313
- errors: result.stderr,
2314
- failures: failures.slice(0, 5), // Limit failures in response
2315
- };
2316
- }
2317
- catch (error) {
2318
- return {
2319
- error: true,
2320
- success: false,
2321
- message: `Failed to run tests: ${error.message}`,
2322
- };
2323
- }
2324
- }
2325
- async getTestStatus() {
2326
- if (!this.lastTestResult) {
2327
- return {
2328
- error: true,
2329
- success: false,
2330
- message: 'No test results available. Run tests first using run_tests.',
2331
- };
2332
- }
2333
- return {
2334
- success: true,
2335
- lastRun: this.lastTestResult,
2336
- };
2337
- }
2338
- // ── AI Test Generation Methods ──
2339
- testGenerator = null;
2340
- getTestGenerator() {
2341
- if (!this.testGenerator) {
2342
- this.testGenerator = new TestGenerator(this.workingDir);
2343
- }
2344
- return this.testGenerator;
2345
- }
2346
- async generateTests(filePath, config, writeToFile = false) {
2347
- try {
2348
- const generator = this.getTestGenerator();
2349
- const absolutePath = this.resolvePath(filePath);
2350
- // Analyze the file
2351
- const analysis = await generator.analyzeFile(absolutePath);
2352
- // Generate tests
2353
- const generatedTest = await generator.generateTests(analysis, {
2354
- framework: config.framework,
2355
- outputDir: config.outputDir,
2356
- includeEdgeCases: config.includeEdgeCases ?? true,
2357
- includeMocks: config.includeMocks ?? true,
2358
- maxTestsPerFunction: config.maxTestsPerFunction ?? 5,
2359
- });
2360
- // Optionally write to file
2361
- if (writeToFile && !this.dryRun) {
2362
- await writeTestFile(generatedTest);
2363
- }
2364
- return {
2365
- success: true,
2366
- sourceFile: filePath,
2367
- testFilePath: generatedTest.testFilePath,
2368
- framework: generatedTest.framework,
2369
- testCasesGenerated: generatedTest.testCases.length,
2370
- coverage: generatedTest.coverage,
2371
- writtenToFile: writeToFile && !this.dryRun,
2372
- dryRun: this.dryRun,
2373
- content: generatedTest.content,
2374
- testCases: generatedTest.testCases.map(tc => ({
2375
- name: tc.name,
2376
- type: tc.type,
2377
- description: tc.description,
2378
- })),
2379
- };
2380
- }
2381
- catch (error) {
2382
- return {
2383
- error: true,
2384
- success: false,
2385
- message: `Failed to generate tests: ${error.message}`,
2386
- };
2387
- }
2388
- }
2389
- async analyzeCodeForTests(filePath) {
2390
- try {
2391
- const generator = this.getTestGenerator();
2392
- const absolutePath = this.resolvePath(filePath);
2393
- const analysis = await generator.analyzeFile(absolutePath);
2394
- return {
2395
- success: true,
2396
- filePath,
2397
- language: analysis.language,
2398
- functions: analysis.functions.map(f => ({
2399
- name: f.name,
2400
- params: f.params,
2401
- returnType: f.returnType,
2402
- isAsync: f.isAsync,
2403
- isExported: f.isExported,
2404
- complexity: f.complexity,
2405
- dependencies: f.dependencies,
2406
- sideEffects: f.sideEffects,
2407
- })),
2408
- classes: analysis.classes.map(c => ({
2409
- name: c.name,
2410
- isExported: c.isExported,
2411
- methodCount: c.methods.length,
2412
- propertyCount: c.properties.length,
2413
- methods: c.methods.map(m => m.name),
2414
- })),
2415
- exports: analysis.exports,
2416
- imports: analysis.imports,
2417
- summary: {
2418
- totalFunctions: analysis.functions.length,
2419
- exportedFunctions: analysis.functions.filter(f => f.isExported).length,
2420
- totalClasses: analysis.classes.length,
2421
- exportedClasses: analysis.classes.filter(c => c.isExported).length,
2422
- asyncFunctions: analysis.functions.filter(f => f.isAsync).length,
2423
- highComplexityFunctions: analysis.functions.filter(f => f.complexity === 'high').length,
2424
- },
2425
- };
2426
- }
2427
- catch (error) {
2428
- return {
2429
- error: true,
2430
- success: false,
2431
- message: `Failed to analyze code: ${error.message}`,
2432
- };
2433
- }
2434
- }
2435
- // ── Git Methods ──
2436
- async getGitStatus() {
2437
- try {
2438
- const status = await this.gitUtils.getStatus();
2439
- return {
2440
- success: true,
2441
- ...status,
2442
- };
2443
- }
2444
- catch (error) {
2445
- return {
2446
- error: true,
2447
- success: false,
2448
- message: `Failed to get git status: ${error.message}`,
2449
- };
2450
- }
2451
- }
2452
- async getGitDiffSummary(target) {
2453
- try {
2454
- const summary = await this.gitUtils.getDiffSummary(target);
2455
- return {
2456
- success: true,
2457
- ...summary,
2458
- };
2459
- }
2460
- catch (error) {
2461
- return {
2462
- error: true,
2463
- success: false,
2464
- message: `Failed to get diff summary: ${error.message}`,
2465
- };
2466
- }
2467
- }
2468
- async getGitChangedFiles(target) {
2469
- try {
2470
- const files = target
2471
- ? await this.gitUtils.getChangedFilesSince(target)
2472
- : await this.gitUtils.getChangedFiles();
2473
- return {
2474
- success: true,
2475
- files,
2476
- count: files.length,
2477
- };
2478
- }
2479
- catch (error) {
2480
- return {
2481
- error: true,
2482
- success: false,
2483
- message: `Failed to get changed files: ${error.message}`,
2484
- };
2485
- }
2486
- }
2487
- async gitCommit(message, agentName) {
2488
- if (this.dryRun) {
2489
- return { success: true, dryRun: true, message: `[DRY RUN] Would commit: "${message}" (Agent: ${agentName})` };
2490
- }
2491
- return this.gitUtils.commit(message, agentName);
2492
- }
2493
- async gitBlameAi(filePath) {
2494
- const result = await this.gitUtils.getBlame(filePath);
2495
- return { success: true, blame: result };
2496
- }
2497
- async createGitCheckpoint(message, strategy) {
2498
- if (this.dryRun) {
2499
- return {
2500
- success: true,
2501
- dryRun: true,
2502
- message: `[DRY RUN] Would create ${strategy || 'stash'} checkpoint: "${message}"`,
2503
- };
2504
- }
2505
- try {
2506
- const result = await this.gitUtils.createCheckpoint(message, strategy);
2507
- if (!result.success) {
2508
- return {
2509
- error: true,
2510
- success: false,
2511
- message: result.error || 'Failed to create checkpoint',
2512
- };
2513
- }
2514
- return {
2515
- success: true,
2516
- checkpoint: result.checkpoint,
2517
- message: `Checkpoint created: ${result.checkpoint?.id}`,
2518
- };
2519
- }
2520
- catch (error) {
2521
- return {
2522
- error: true,
2523
- success: false,
2524
- message: `Failed to create checkpoint: ${error.message}`,
2525
- };
2526
- }
2527
- }
2528
- async revertToGitCheckpoint(checkpointId, checkpointType, confirm) {
2529
- if (this.dryRun) {
2530
- return {
2531
- success: true,
2532
- dryRun: true,
2533
- message: `[DRY RUN] Would revert to ${checkpointType} checkpoint: ${checkpointId}`,
2534
- };
2535
- }
2536
- if (!confirm) {
2537
- return {
2538
- error: true,
2539
- success: false,
2540
- message: 'Revert requires explicit confirmation. Set confirm: true',
2541
- };
2542
- }
2543
- try {
2544
- const checkpoint = {
2545
- type: checkpointType,
2546
- id: checkpointId,
2547
- message: '',
2548
- timestamp: new Date(),
2549
- };
2550
- const result = await this.gitUtils.revertToCheckpoint(checkpoint, confirm);
2551
- if (!result.success) {
2552
- return {
2553
- error: true,
2554
- success: false,
2555
- message: result.error || 'Failed to revert',
2556
- };
2557
- }
2558
- return {
2559
- success: true,
2560
- message: `Reverted to checkpoint: ${checkpointId}`,
2561
- };
2562
- }
2563
- catch (error) {
2564
- return {
2565
- error: true,
2566
- success: false,
2567
- message: `Failed to revert: ${error.message}`,
2568
- };
2569
- }
2570
- }
2571
- async gitShowDiff(filePath, target) {
2572
- try {
2573
- const diff = await this.gitUtils.getUnifiedDiff(filePath, target);
2574
- return {
2575
- success: true,
2576
- diff,
2577
- file: filePath,
2578
- target: target || 'HEAD',
2579
- };
2580
- }
2581
- catch (error) {
2582
- return {
2583
- error: true,
2584
- success: false,
2585
- message: `Failed to get diff: ${error.message}`,
2586
- };
2587
- }
2588
- }
2589
- async mcpAuth(p) {
2590
- const action = typeof p.action === 'string' ? p.action : 'start';
2591
- const server = typeof p.server === 'string' ? p.server : null;
2592
- if (!server) {
2593
- return { error: true, success: false, message: 'Missing required parameter: server (string)' };
2594
- }
2595
- const { ConfigManager } = await import('../utils/config.js');
2596
- const config = new ConfigManager();
2597
- const servers = await config.getMCPServers();
2598
- const serverConfig = servers[server];
2599
- if (!serverConfig) {
2600
- return { error: true, success: false, message: `Unknown MCP server: ${server}` };
2601
- }
2602
- if (!serverConfig.oauth) {
2603
- return { error: true, success: false, message: `MCP server ${server} has no oauth config` };
2604
- }
2605
- if (action === 'start') {
2606
- const started = await this.mcpOAuth.start(server, serverConfig);
2607
- return {
2608
- success: true,
2609
- server,
2610
- action,
2611
- ...started,
2612
- };
2613
- }
2614
- if (action === 'finish') {
2615
- const callbackUrl = typeof p.callback_url === 'string' ? p.callback_url : typeof p.callbackUrl === 'string' ? p.callbackUrl : null;
2616
- if (!callbackUrl) {
2617
- return { error: true, success: false, message: 'Missing required parameter: callback_url (string) for action=finish' };
2618
- }
2619
- const finished = await this.mcpOAuth.finish(server, callbackUrl);
2620
- return { success: finished.success, server, action, message: finished.message };
2621
- }
2622
- return { error: true, success: false, message: `Invalid action: ${action}. Use start|finish.` };
2623
- }
2624
- async getMCPStatus() {
2625
- if (!this.mcpClientManager) {
2626
- return {
2627
- success: true,
2628
- configured: 0,
2629
- connected: 0,
2630
- servers: [],
2631
- tools: [],
2632
- };
2633
- }
2634
- try {
2635
- const connectedServers = this.mcpClientManager.getConnectedServers();
2636
- const allTools = this.mcpClientManager.getAvailableTools();
2637
- const allResources = this.mcpClientManager.getAvailableResources();
2638
- const allPrompts = this.mcpClientManager.getAvailablePrompts();
2639
- const states = this.mcpClientManager.getServerStates?.() || [];
2640
- // Get configured servers from config
2641
- const { ConfigManager } = await import('../utils/config.js');
2642
- const config = new ConfigManager();
2643
- const configuredServers = await config.getMCPServers();
2644
- const servers = Object.entries(configuredServers).map(([serverName, serverConfig]) => {
2645
- const isConnected = connectedServers.includes(serverName);
2646
- const serverTools = allTools.filter(t => t.serverName === serverName);
2647
- const serverResources = allResources.filter(r => r.serverName === serverName);
2648
- const serverPrompts = allPrompts.filter(p => p.serverName === serverName);
2649
- const state = states.find((s) => s.name === serverName);
2650
- return {
2651
- name: serverName,
2652
- command: serverConfig.command,
2653
- args: serverConfig.args || [],
2654
- connected: isConnected,
2655
- state: state?.state || (isConnected ? 'connected' : 'error'),
2656
- lastError: state?.lastError,
2657
- tools: serverTools.length,
2658
- resources: serverResources.length,
2659
- prompts: serverPrompts.length,
2660
- toolNames: serverTools.map(t => t.name),
2661
- error: isConnected ? null : 'Not connected (server executable may not be installed)',
2662
- };
2663
- });
2664
- return {
2665
- success: true,
2666
- configured: Object.keys(configuredServers).length,
2667
- connected: connectedServers.length,
2668
- servers,
2669
- totalTools: allTools.length,
2670
- totalResources: allResources.length,
2671
- totalPrompts: allPrompts.length,
2672
- };
2673
- }
2674
- catch (error) {
2675
- return {
2676
- error: true,
2677
- success: false,
2678
- message: `Failed to get MCP status: ${error?.message || String(error)}`,
2679
- };
2680
- }
2681
- }
2682
- async searchSkillsSh(query) {
2683
- try {
2684
- const results = await this.skillManager.searchSkillsSh(query);
2685
- return {
2686
- success: true,
2687
- query,
2688
- count: results.length,
2689
- results: results.map(r => ({ id: r.id, url: r.url })),
2690
- };
2691
- }
2692
- catch (error) {
2693
- return { error: true, success: false, message: `Failed to search skills.sh: ${error.message}` };
2694
- }
2695
- }
2696
- async installSkillFromSkillsSh(skillId) {
2697
- try {
2698
- const result = await this.skillManager.installFromSkillsSh(skillId);
2699
- if (result.success) {
2700
- return {
2701
- success: true,
2702
- message: result.message || `Successfully installed skill: ${skillId}`,
2703
- skill_id: skillId,
2704
- filePath: result.filePath,
2705
- };
2706
- }
2707
- else {
2708
- return { error: true, success: false, message: `Failed to install skill: ${result.message}` };
2709
- }
2710
- }
2711
- catch (error) {
2712
- return { error: true, success: false, message: `Exception during installation: ${error.message}` };
2713
- }
2714
- }
2715
- // ── grep_code: ripgrep / grep fallback ──────────────────────
2716
- async grepCode(pattern, searchPath = '.', ignoreCase = false, filePattern, maxResults = 50) {
2717
- const fullPath = this.resolvePath(searchPath);
2718
- const caseFlag = ignoreCase ? '-i' : '';
2719
- // Try ripgrep first, fallback to grep
2720
- const tryRg = async () => {
2721
- const includeFlag = filePattern ? `--glob '${filePattern}'` : '';
2722
- const cmd = `rg --no-heading --line-number --max-count ${maxResults} ${caseFlag} ${includeFlag} -- ${JSON.stringify(pattern)} ${JSON.stringify(fullPath)} 2>/dev/null`;
2723
- const { stdout } = await execAsync(cmd, { maxBuffer: 1024 * 1024, timeout: 15000 });
2724
- return stdout;
2725
- };
2726
- const tryGrep = async () => {
2727
- const includeFlag = filePattern ? `--include='${filePattern}'` : '';
2728
- const cmd = `grep -rnI ${caseFlag} ${includeFlag} -- ${JSON.stringify(pattern)} ${JSON.stringify(fullPath)} 2>/dev/null | head -${maxResults}`;
2729
- const { stdout } = await execAsync(cmd, { maxBuffer: 1024 * 1024, timeout: 15000 });
2730
- return stdout;
2731
- };
2732
- try {
2733
- let output;
2734
- try {
2735
- output = await tryRg();
2736
- }
2737
- catch {
2738
- output = await tryGrep();
2739
- }
2740
- const lines = output.trim().split('\n').filter(Boolean);
2741
- const matches = lines.map(line => {
2742
- const match = line.match(/^(.+?):(\d+):(.*)$/);
2743
- if (match) {
2744
- return {
2745
- file: path.relative(this.workingDir, match[1]),
2746
- line: parseInt(match[2], 10),
2747
- content: match[3].trim(),
2748
- };
2749
- }
2750
- return { raw: line };
2751
- });
2752
- return {
2753
- success: true,
2754
- pattern,
2755
- total: matches.length,
2756
- matches,
2757
- };
2758
- }
2759
- catch (error) {
2760
- // grep returns exit code 1 when no matches found
2761
- if (error.code === 1 || error.message?.includes('exit code 1')) {
2762
- return { success: true, pattern, total: 0, matches: [], message: 'No matches found' };
2763
- }
2764
- return { error: true, success: false, message: `Grep failed: ${error.message}` };
2765
- }
2766
- }
2767
- // ── web_search: DuckDuckGo HTML ───────────────────────────
2768
- async webSearch(query, maxResults = 8) {
2769
- try {
2770
- const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
2771
- const response = await fetch(url, {
2772
- method: 'POST',
2773
- headers: {
2774
- 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
2775
- 'Accept': 'text/html',
2776
- 'Content-Type': 'application/x-www-form-urlencoded',
2777
- },
2778
- body: `q=${encodeURIComponent(query)}`,
2779
- });
2780
- if (!response.ok) {
2781
- return { error: true, success: false, message: `Search failed: HTTP ${response.status}` };
2782
- }
2783
- const html = await response.text();
2784
- // Parse results from DuckDuckGo HTML
2785
- const results = [];
2786
- const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>(.*?)<\/a>/gi;
2787
- let match;
2788
- while ((match = resultRegex.exec(html)) !== null && results.length < maxResults) {
2789
- const rawUrl = match[1];
2790
- const title = match[2].replace(/<[^>]*>/g, '').trim();
2791
- const snippet = match[3].replace(/<[^>]*>/g, '').trim();
2792
- // DuckDuckGo wraps URLs in redirect; extract actual URL
2793
- let actualUrl = rawUrl;
2794
- const uddgMatch = rawUrl.match(/uddg=([^&]+)/);
2795
- if (uddgMatch) {
2796
- actualUrl = decodeURIComponent(uddgMatch[1]);
2797
- }
2798
- if (title && actualUrl) {
2799
- results.push({ title, url: actualUrl, snippet });
2800
- }
2801
- }
2802
- // Fallback: try simpler pattern if regex didn't match
2803
- if (results.length === 0) {
2804
- const simpleLinkRegex = /<a[^>]*class="result__url"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi;
2805
- while ((match = simpleLinkRegex.exec(html)) !== null && results.length < maxResults) {
2806
- const url = match[1].trim();
2807
- const title = match[2].replace(/<[^>]*>/g, '').trim();
2808
- if (url && title) {
2809
- results.push({ title, url, snippet: '' });
2810
- }
2811
- }
2812
- }
2813
- return {
2814
- success: true,
2815
- query,
2816
- total: results.length,
2817
- results,
2818
- };
2819
- }
2820
- catch (error) {
2821
- return { error: true, success: false, message: `Web search failed: ${error.message}` };
2822
- }
2823
- }
2824
- // ── fetch_url: read any URL as text ────────────────────────
2825
- async fetchUrl(url, maxLength = 20000) {
2826
- const urlResult = sanitizeUrl(url.trim());
2827
- if (!urlResult.ok) {
2828
- return { error: true, success: false, message: urlResult.message };
2829
- }
2830
- try {
2831
- const response = await fetch(urlResult.url, {
2832
- headers: {
2833
- 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
2834
- 'Accept': 'text/html,application/xhtml+xml,text/plain,application/json',
2835
- },
2836
- signal: AbortSignal.timeout(15000),
2837
- });
2838
- if (!response.ok) {
2839
- return { error: true, success: false, message: `Fetch failed: HTTP ${response.status} ${response.statusText}` };
2840
- }
2841
- const contentType = response.headers.get('content-type') || '';
2842
- const raw = await response.text();
2843
- let text;
2844
- if (contentType.includes('application/json')) {
2845
- // Return JSON as-is
2846
- text = raw;
2847
- }
2848
- else if (contentType.includes('text/plain')) {
2849
- text = raw;
2850
- }
2851
- else {
2852
- // Strip HTML to text
2853
- text = raw
2854
- .replace(/<script[\s\S]*?<\/script>/gi, '')
2855
- .replace(/<style[\s\S]*?<\/style>/gi, '')
2856
- .replace(/<nav[\s\S]*?<\/nav>/gi, '')
2857
- .replace(/<footer[\s\S]*?<\/footer>/gi, '')
2858
- .replace(/<header[\s\S]*?<\/header>/gi, '')
2859
- .replace(/<[^>]+>/g, '\n')
2860
- .replace(/&nbsp;/g, ' ')
2861
- .replace(/&amp;/g, '&')
2862
- .replace(/&lt;/g, '<')
2863
- .replace(/&gt;/g, '>')
2864
- .replace(/&quot;/g, '"')
2865
- .replace(/&#39;/g, "'")
2866
- .replace(/\n{3,}/g, '\n\n')
2867
- .trim();
2868
- }
2869
- // Truncate to maxLength
2870
- const truncated = text.length > maxLength;
2871
- const content = truncated ? text.slice(0, maxLength) + '\n... [truncated]' : text;
2872
- return {
2873
- success: true,
2874
- url: urlResult.url,
2875
- contentType: contentType.split(';')[0],
2876
- length: text.length,
2877
- truncated,
2878
- content,
2879
- };
2880
- }
2881
- catch (error) {
2882
- return { error: true, success: false, message: `Fetch failed: ${error.message}` };
2883
- }
2884
- }
2885
- // ── update_memory: persist project knowledge ───────────────
2886
- async updateMemory(content, append = true) {
2887
- const memoryDir = path.join(this.workingDir, '.xibecode');
2888
- const memoryPath = path.join(memoryDir, 'memory.md');
2889
- try {
2890
- await fs.mkdir(memoryDir, { recursive: true });
2891
- if (append) {
2892
- let existing = '';
2893
- try {
2894
- existing = await fs.readFile(memoryPath, 'utf-8');
2895
- }
2896
- catch { /* file doesn't exist yet */ }
2897
- const updated = existing ? `${existing.trimEnd()}\n\n${content}` : content;
2898
- await fs.writeFile(memoryPath, updated, 'utf-8');
2899
- }
2900
- else {
2901
- await fs.writeFile(memoryPath, content, 'utf-8');
2902
- }
2903
- const final = await fs.readFile(memoryPath, 'utf-8');
2904
- return {
2905
- success: true,
2906
- path: '.xibecode/memory.md',
2907
- lines: final.split('\n').length,
2908
- message: append ? 'Memory updated (appended)' : 'Memory replaced',
2909
- };
2910
- }
2911
- catch (error) {
2912
- return { error: true, success: false, message: `Failed to update memory: ${error.message}` };
2913
- }
2914
- }
2915
- }
2916
- //# sourceMappingURL=tools.js.map