@causyne/agent 0.0.0-stage → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/LICENSE +102 -0
  2. package/README.md +31 -2
  3. package/SECURITY.md +24 -0
  4. package/bin/causyne.js +13 -0
  5. package/docs/ARCHITECTURE.md +63 -0
  6. package/docs/PROVENANCE.md +29 -0
  7. package/docs/SECURITY_REVIEW.md +89 -0
  8. package/docs/THIRD_PARTY_NOTICES.md +49 -0
  9. package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
  10. package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
  11. package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
  12. package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
  13. package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
  14. package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
  15. package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
  16. package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
  17. package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
  18. package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
  19. package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
  20. package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
  21. package/native-skills/android/play-store-release/SKILL.md +43 -0
  22. package/native-skills/android/room-and-persistence/SKILL.md +40 -0
  23. package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
  24. package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
  25. package/native-skills/backend/caching-strategy/SKILL.md +44 -0
  26. package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
  27. package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
  28. package/native-skills/backend/rest-api-design/SKILL.md +40 -0
  29. package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
  30. package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
  31. package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
  32. package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
  33. package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
  34. package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
  35. package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
  36. package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
  37. package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
  38. package/native-skills/data/metric-definition/SKILL.md +42 -0
  39. package/native-skills/data/notebook-to-production/SKILL.md +43 -0
  40. package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
  41. package/native-skills/database/connection-pooling/SKILL.md +42 -0
  42. package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
  43. package/native-skills/database/query-optimisation/SKILL.md +38 -0
  44. package/native-skills/database/safe-migrations/SKILL.md +47 -0
  45. package/native-skills/database/schema-design/SKILL.md +38 -0
  46. package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
  47. package/native-skills/design/colour-system-construction/SKILL.md +38 -0
  48. package/native-skills/design/design-direction-brief/SKILL.md +41 -0
  49. package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
  50. package/native-skills/design/responsive-strategy/SKILL.md +39 -0
  51. package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
  52. package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
  53. package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
  54. package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
  55. package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
  56. package/native-skills/devops/incident-triage/SKILL.md +38 -0
  57. package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
  58. package/native-skills/devops/observability-baseline/SKILL.md +38 -0
  59. package/native-skills/devops/secret-management/SKILL.md +40 -0
  60. package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
  61. package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
  62. package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
  63. package/native-skills/git/branch-strategy/SKILL.md +39 -0
  64. package/native-skills/git/commit-hygiene/SKILL.md +41 -0
  65. package/native-skills/git/history-recovery/SKILL.md +44 -0
  66. package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
  67. package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
  68. package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
  69. package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
  70. package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
  71. package/native-skills/mobile/deep-linking/SKILL.md +39 -0
  72. package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
  73. package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
  74. package/native-skills/mobile/push-notifications/SKILL.md +38 -0
  75. package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
  76. package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
  77. package/native-skills/performance/load-testing/SKILL.md +42 -0
  78. package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
  79. package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
  80. package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
  81. package/native-skills/python/async-correctness/SKILL.md +40 -0
  82. package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
  83. package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
  84. package/native-skills/python/environment-isolation/SKILL.md +41 -0
  85. package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
  86. package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
  87. package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
  88. package/native-skills/security/authorization-model/SKILL.md +42 -0
  89. package/native-skills/security/crypto-usage/SKILL.md +48 -0
  90. package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
  91. package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
  92. package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
  93. package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
  94. package/native-skills/security/threat-modelling/SKILL.md +46 -0
  95. package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
  96. package/native-skills/testing/property-based-testing/SKILL.md +46 -0
  97. package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
  98. package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
  99. package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
  100. package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
  101. package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
  102. package/native-skills/web/accessible-components/SKILL.md +45 -0
  103. package/native-skills/web/api-client-resilience/SKILL.md +39 -0
  104. package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
  105. package/native-skills/web/core-web-vitals/SKILL.md +40 -0
  106. package/native-skills/web/css-layout-systems/SKILL.md +41 -0
  107. package/native-skills/web/form-validation-ux/SKILL.md +39 -0
  108. package/native-skills/web/react-state-architecture/SKILL.md +41 -0
  109. package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
  110. package/package.json +68 -4
  111. package/src/agentLoop.js +2458 -0
  112. package/src/appChannel.js +109 -0
  113. package/src/approvalGrants.js +66 -0
  114. package/src/auditLog.js +52 -0
  115. package/src/autoUpdate.js +12 -0
  116. package/src/capabilities.js +36 -0
  117. package/src/checkpoints.js +1199 -0
  118. package/src/cliMode.js +638 -0
  119. package/src/commands.js +805 -0
  120. package/src/compiledSkills.js +540 -0
  121. package/src/config.js +130 -0
  122. package/src/contextManager.js +498 -0
  123. package/src/cortex/autoCondenser.js +192 -0
  124. package/src/cortex/provisioner.js +27 -0
  125. package/src/cortex/vectorDb.js +447 -0
  126. package/src/designPalettes.js +99 -0
  127. package/src/devMode.js +6 -0
  128. package/src/embodimentSession.js +180 -0
  129. package/src/entrypoint.js +194 -0
  130. package/src/execSafe.js +21 -0
  131. package/src/gitSnapshot.js +378 -0
  132. package/src/mcp/jsonRpc.js +165 -0
  133. package/src/mcp/mcpClient.js +136 -0
  134. package/src/mcp/mcpRegistry.js +207 -0
  135. package/src/memory.js +62 -0
  136. package/src/memoryManager.js +124 -0
  137. package/src/modelCatalogue.js +132 -0
  138. package/src/modelIds.js +42 -0
  139. package/src/modelRouter.js +145 -0
  140. package/src/outputStore.js +246 -0
  141. package/src/perception.js +124 -0
  142. package/src/planCache.js +437 -0
  143. package/src/procSignal.js +32 -0
  144. package/src/providers/anthropic.js +408 -0
  145. package/src/providers/base.js +328 -0
  146. package/src/providers/dashscope-byok.js +86 -0
  147. package/src/providers/deepseek.js +80 -0
  148. package/src/providers/factory.js +37 -0
  149. package/src/providers/gemini.js +346 -0
  150. package/src/providers/moonshot.js +73 -0
  151. package/src/providers/muse.js +169 -0
  152. package/src/providers/ollama.js +104 -0
  153. package/src/providers/openai-compatible.js +84 -0
  154. package/src/providers/openai.js +20 -0
  155. package/src/providers/qwen-byok.js +76 -0
  156. package/src/providers/xai.js +73 -0
  157. package/src/provisioning/capabilityConsent.js +24 -0
  158. package/src/provisioning/capabilityProvisioner.js +271 -0
  159. package/src/provisioning/capabilityTools.js +29 -0
  160. package/src/reporting.js +373 -0
  161. package/src/sandbox.js +529 -0
  162. package/src/sessionStore.js +279 -0
  163. package/src/skillsManager.js +288 -0
  164. package/src/slopDetector.js +307 -0
  165. package/src/taskBuffer.js +45 -0
  166. package/src/taskShape.js +241 -0
  167. package/src/textDiff.js +150 -0
  168. package/src/tokenTracker.js +560 -0
  169. package/src/tool-scripts/ax_snapshot.applescript +39 -0
  170. package/src/tool-scripts/ax_snapshot.ps1 +10 -0
  171. package/src/tool-scripts/desktop_act.applescript +58 -0
  172. package/src/tool-scripts/resolve_automator.py +119 -0
  173. package/src/toolPacks.js +524 -0
  174. package/src/tools/archive.js +63 -0
  175. package/src/tools/astryxOracle.js +78 -0
  176. package/src/tools/axSnapshot.js +165 -0
  177. package/src/tools/blenderAutomator.js +349 -0
  178. package/src/tools/browserAutomator.js +252 -0
  179. package/src/tools/checkSystemSpecs.js +64 -0
  180. package/src/tools/clipboard.js +36 -0
  181. package/src/tools/codeSearchAst.js +101 -0
  182. package/src/tools/codeSearcher.js +230 -0
  183. package/src/tools/codeTools.js +262 -0
  184. package/src/tools/codebaseSearch.js +75 -0
  185. package/src/tools/createSkill.js +129 -0
  186. package/src/tools/deferredMcpSearch.js +246 -0
  187. package/src/tools/desktopAct.js +208 -0
  188. package/src/tools/desktopOpen.js +34 -0
  189. package/src/tools/dynamicLoader.js +44 -0
  190. package/src/tools/executeProceduralMedia.js +93 -0
  191. package/src/tools/fsDelete.js +51 -0
  192. package/src/tools/fsEdit.js +105 -0
  193. package/src/tools/fsFind.js +128 -0
  194. package/src/tools/fsList.js +77 -0
  195. package/src/tools/fsMove.js +57 -0
  196. package/src/tools/fsRead.js +87 -0
  197. package/src/tools/fsSearch.js +198 -0
  198. package/src/tools/fsWrite.js +41 -0
  199. package/src/tools/gameAiBehaviorTree.js +208 -0
  200. package/src/tools/gameAnimationRig.js +194 -0
  201. package/src/tools/gameAssetFetcher.js +372 -0
  202. package/src/tools/gameAssetHarvester.js +210 -0
  203. package/src/tools/gameAudioSynth.js +267 -0
  204. package/src/tools/gameBuilder.js +886 -0
  205. package/src/tools/gameCanvasAudit.js +101 -0
  206. package/src/tools/gameCinematicSequencer.js +151 -0
  207. package/src/tools/gameDataForge.js +124 -0
  208. package/src/tools/gameDialogueTreeGen.js +166 -0
  209. package/src/tools/gameEconomyBalancer.js +161 -0
  210. package/src/tools/gameEntitySpawner.js +207 -0
  211. package/src/tools/gameLoreForge.js +153 -0
  212. package/src/tools/gameQuestArcBuilder.js +152 -0
  213. package/src/tools/gameSceneBuilder.js +158 -0
  214. package/src/tools/gameShaderForge.js +181 -0
  215. package/src/tools/gameTilemapGenerator.js +164 -0
  216. package/src/tools/gameUiHudBuilder.js +292 -0
  217. package/src/tools/gameVfxEmitterGen.js +108 -0
  218. package/src/tools/gameWebRuntimeInspector.js +91 -0
  219. package/src/tools/generateMediaViaApi.js +358 -0
  220. package/src/tools/gitExec.js +54 -0
  221. package/src/tools/godotAutomator.js +437 -0
  222. package/src/tools/manageLocalAiDependencies.js +220 -0
  223. package/src/tools/memoryWrite.js +99 -0
  224. package/src/tools/multiEdit.js +107 -0
  225. package/src/tools/palmierAutomator.js +104 -0
  226. package/src/tools/pdfReader.js +112 -0
  227. package/src/tools/persistentShell.js +148 -0
  228. package/src/tools/printer.js +316 -0
  229. package/src/tools/procTerm.js +91 -0
  230. package/src/tools/processList.js +52 -0
  231. package/src/tools/readNativeSkill.js +39 -0
  232. package/src/tools/registry.js +277 -0
  233. package/src/tools/resolveAutomator.js +102 -0
  234. package/src/tools/screenshot.js +75 -0
  235. package/src/tools/subagentOrchestrator.js +305 -0
  236. package/src/tools/sysRun.js +125 -0
  237. package/src/tools/todoWrite.js +75 -0
  238. package/src/tools/toolDispatch.js +127 -0
  239. package/src/tools/toolForge.js +124 -0
  240. package/src/tools/updatePlan.js +88 -0
  241. package/src/tools/viewImage.js +82 -0
  242. package/src/tools/visionInspector.js +154 -0
  243. package/src/tools/webFetch.js +74 -0
  244. package/src/tools/webSearch.js +82 -0
  245. package/src/tui/ask.js +164 -0
  246. package/src/tui/banner.js +158 -0
  247. package/src/tui/block.js +152 -0
  248. package/src/tui/chatRenderer.js +953 -0
  249. package/src/tui/completion.js +424 -0
  250. package/src/tui/dialogs.js +606 -0
  251. package/src/tui/diff.js +296 -0
  252. package/src/tui/history.js +135 -0
  253. package/src/tui/home.js +217 -0
  254. package/src/tui/invocation.js +105 -0
  255. package/src/tui/keys.js +443 -0
  256. package/src/tui/lineEditor.js +256 -0
  257. package/src/tui/overlay.js +256 -0
  258. package/src/tui/prompt.js +1258 -0
  259. package/src/tui/selectList.js +335 -0
  260. package/src/tui/stage.js +330 -0
  261. package/src/tui/stageSurface.js +609 -0
  262. package/src/tui/statusLine.js +254 -0
  263. package/src/tui/text.js +345 -0
  264. package/src/tui/theme.js +393 -0
  265. package/src/tui/toolDisplay.js +176 -0
  266. package/src/tui/transcript.js +235 -0
  267. package/src/tui/viewport.js +337 -0
  268. package/src/uncertainty.js +271 -0
  269. package/src/userSkills.js +265 -0
  270. package/src/utils/cli_ui.js +0 -0
  271. package/src/utils/inquirer.js +12 -0
  272. package/src/utils/paths.js +37 -0
  273. package/src/version.js +38 -0
  274. package/src/vfeTracker.js +220 -0
@@ -0,0 +1,252 @@
1
+ import os from 'os';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { ensureCapability, loadCapabilityPackage } from '../provisioning/capabilityProvisioner.js';
5
+ import { causyneHome } from '../utils/paths.js';
6
+
7
+ function getChromeExecutablePath() {
8
+ const platform = os.platform();
9
+ if (platform === 'win32') {
10
+ return 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe';
11
+ } else if (platform === 'darwin') {
12
+ return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
13
+ } else {
14
+ return '/usr/bin/google-chrome';
15
+ }
16
+ }
17
+
18
+ function getChromeUserDataDir() {
19
+ const platform = os.platform();
20
+ const home = os.homedir();
21
+ if (platform === 'win32') {
22
+ return `${home}\\AppData\\Local\\Google\\Chrome\\User Data`;
23
+ } else if (platform === 'darwin') {
24
+ return `${home}/Library/Application Support/Google/Chrome`;
25
+ } else {
26
+ return `${home}/.config/google-chrome`;
27
+ }
28
+ }
29
+
30
+ let activeBrowser = null;
31
+ let activePage = null;
32
+
33
+ export const browserAct = {
34
+ schema: {
35
+ name: 'browser.act',
36
+ description: 'Automate Google Chrome using local profile to scrape sites or perform actions. IMPORTANT: First use "scrape" to get the page mapped to numerical indices. Then use "clickIndex" or "typeIndex" for flawless precision. Defaults to ~/.causyne/chrome_profile to protect your personal accounts from logout.',
37
+ parameters: {
38
+ type: 'object',
39
+ properties: {
40
+ usePersonalProfile: {
41
+ type: 'boolean',
42
+ description: 'Set to true ONLY if you explicitly want to use your personal Google Chrome profile. Defaults to false (uses safe dedicated profile ~/.causyne/chrome_profile).'
43
+ },
44
+ profileDirectory: {
45
+ type: 'string',
46
+ description: 'Specific profile sub-folder when usePersonalProfile is true (e.g. "Profile 1", "Default", "Profile 2").'
47
+ },
48
+ actions: {
49
+ type: 'array',
50
+ description: 'List of actions to perform in order',
51
+ items: {
52
+ type: 'object',
53
+ properties: {
54
+ type: { type: 'string', enum: ['goto', 'click', 'type', 'clickIndex', 'typeIndex', 'waitFor', 'scrape', 'selectOption', 'keyPress', 'scroll'] },
55
+ url: { type: 'string' },
56
+ selector: { type: 'string', description: 'CSS selector (legacy)' },
57
+ index: { type: 'number', description: 'Element index from the scrape map' },
58
+ text: { type: 'string' },
59
+ value: { type: 'string', description: 'Option value to select in dropdowns' },
60
+ key: { type: 'string', description: 'Key to press (Enter, Tab, ArrowDown, etc.)' },
61
+ humanDelayMs: { type: 'number', description: 'Keystroke delay in ms for natural human-like typing (default 45ms)' },
62
+ timeoutMs: { type: 'number' }
63
+ },
64
+ required: ['type']
65
+ }
66
+ }
67
+ },
68
+ required: ['actions']
69
+ }
70
+ },
71
+ async execute(args) {
72
+ const vision = await ensureCapability('vision');
73
+ if (!vision.ok) return { success: false, error: vision.reason, requiresConsent: vision.requiresConsent };
74
+ const puppeteer = await loadCapabilityPackage('puppeteer-core');
75
+
76
+ let results = [];
77
+
78
+ try {
79
+ if (!activeBrowser || !activeBrowser.isConnected()) {
80
+ const executablePath = getChromeExecutablePath();
81
+ const primaryUserDataDir = getChromeUserDataDir();
82
+ let connectedOrLaunched = false;
83
+
84
+ // Layer 1: Attempt connection to active Chrome Remote Debugging port (9222) for zero-interruption live session reuse
85
+ try {
86
+ const controller = new AbortController();
87
+ const timeoutId = setTimeout(() => controller.abort(), 600);
88
+ const resp = await fetch('http://127.0.0.1:9222/json/version', { signal: controller.signal });
89
+ clearTimeout(timeoutId);
90
+ if (resp.ok) {
91
+ const data = await resp.json();
92
+ if (data.webSocketDebuggerUrl) {
93
+ activeBrowser = await puppeteer.connect({
94
+ browserWSEndpoint: data.webSocketDebuggerUrl,
95
+ defaultViewport: null
96
+ });
97
+ connectedOrLaunched = true;
98
+ results.push('[Sovereign Browser] Attached directly to active Chrome session via port 9222 (Layer 1).');
99
+ }
100
+ }
101
+ } catch (debugErr) {
102
+ // Port 9222 not active, proceed to Layer 2 / Layer 3 decision
103
+ }
104
+
105
+ // Sovereign Guard: Decide whether to launch dedicated profile (Layer 2) or personal profile (Layer 3)
106
+ if (!connectedOrLaunched) {
107
+ const fallbackDir = path.join(causyneHome(), 'chrome_profile');
108
+ if (!fs.existsSync(fallbackDir)) fs.mkdirSync(fallbackDir, { recursive: true });
109
+
110
+ if (!args.usePersonalProfile) {
111
+ // Layer 2: Sovereign Dedicated Agent Profile (100% Safe - Never risks logging out personal Chrome accounts)
112
+ activeBrowser = await puppeteer.launch({
113
+ executablePath,
114
+ userDataDir: fallbackDir,
115
+ headless: false,
116
+ defaultViewport: null,
117
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
118
+ });
119
+ connectedOrLaunched = true;
120
+ results.push('[Sovereign Browser] Launched Chrome using Sovereign Dedicated Profile (~/.causyne/chrome_profile). Your personal Google Chrome accounts remain 100% untouched and protected.');
121
+ } else {
122
+ // Layer 3: Explicit Personal Profile Guard (Strictly when usePersonalProfile: true is requested)
123
+ try {
124
+ const profileArgs = ['--no-sandbox', '--disable-setuid-sandbox'];
125
+ if (args.profileDirectory) {
126
+ profileArgs.push(`--profile-directory=${args.profileDirectory}`);
127
+ }
128
+ activeBrowser = await puppeteer.launch({
129
+ executablePath,
130
+ userDataDir: primaryUserDataDir,
131
+ headless: false,
132
+ defaultViewport: null,
133
+ args: profileArgs
134
+ });
135
+ connectedOrLaunched = true;
136
+ results.push(`[Sovereign Browser] Launched primary profile (${args.profileDirectory || 'Default'}) explicitly requested via usePersonalProfile.`);
137
+ } catch (launchErr) {
138
+ const isProfileLock = launchErr.message.includes('already running') ||
139
+ launchErr.message.includes('SingletonLock') ||
140
+ launchErr.message.includes('EBUSY') ||
141
+ launchErr.message.includes('lock') ||
142
+ launchErr.message.includes('user data directory is already in use');
143
+ if (isProfileLock) {
144
+ // Safe fallback to dedicated profile so task never fails and never corrupts personal cookies
145
+ results.push(`[Sovereign Profile Guard] Your personal Chrome profile (${args.profileDirectory || 'Default'}) is currently running and locked without remote debugging port 9222. Automatically switched to Sovereign Dedicated Profile (~/.causyne/chrome_profile) to protect your personal account session from logout.`);
146
+ activeBrowser = await puppeteer.launch({
147
+ executablePath,
148
+ userDataDir: fallbackDir,
149
+ headless: false,
150
+ defaultViewport: null,
151
+ args: ['--no-sandbox', '--disable-setuid-sandbox']
152
+ });
153
+ connectedOrLaunched = true;
154
+ } else {
155
+ throw launchErr;
156
+ }
157
+ }
158
+ }
159
+ }
160
+
161
+ const pages = await activeBrowser.pages();
162
+ activePage = pages[0] || (await activeBrowser.newPage());
163
+ }
164
+
165
+ for (const action of args.actions) {
166
+ if (action.type === 'goto') {
167
+ await activePage.goto(action.url, { waitUntil: 'networkidle2' });
168
+ results.push(`Navigated to ${action.url}`);
169
+ }
170
+ else if (action.type === 'click') {
171
+ await activePage.waitForSelector(action.selector, { timeout: action.timeoutMs || 5000 });
172
+ await activePage.click(action.selector);
173
+ results.push(`Clicked ${action.selector}`);
174
+ }
175
+ else if (action.type === 'type') {
176
+ await activePage.waitForSelector(action.selector, { timeout: action.timeoutMs || 5000 });
177
+ await activePage.type(action.selector, action.text, { delay: action.humanDelayMs || 45 });
178
+ results.push(`Typed text into ${action.selector} with human keystroke delay`);
179
+ }
180
+ // Phase 1 AGI Upgrade: DOM-to-Markdown Interaction
181
+ else if (action.type === 'clickIndex') {
182
+ const selector = `[data-causyne-id="${action.index}"]`;
183
+ await activePage.waitForSelector(selector, { timeout: action.timeoutMs || 5000 });
184
+ await activePage.click(selector);
185
+ results.push(`Clicked element mapped to index [${action.index}]`);
186
+ }
187
+ else if (action.type === 'typeIndex') {
188
+ const selector = `[data-causyne-id="${action.index}"]`;
189
+ await activePage.waitForSelector(selector, { timeout: action.timeoutMs || 5000 });
190
+ await activePage.type(selector, action.text, { delay: action.humanDelayMs || 45 });
191
+ results.push(`Typed text into element mapped to index [${action.index}] with human keystroke delay`);
192
+ }
193
+ else if (action.type === 'selectOption') {
194
+ if (action.index !== undefined) {
195
+ const selector = `[data-causyne-id="${action.index}"]`;
196
+ await activePage.waitForSelector(selector, { timeout: action.timeoutMs || 5000 });
197
+ await activePage.select(selector, action.value || action.text);
198
+ } else if (action.selector) {
199
+ await activePage.waitForSelector(action.selector, { timeout: action.timeoutMs || 5000 });
200
+ await activePage.select(action.selector, action.value || action.text);
201
+ }
202
+ results.push(`Selected option [${action.value || action.text}] from dropdown`);
203
+ }
204
+ else if (action.type === 'keyPress') {
205
+ await activePage.keyboard.press(action.key || 'Enter');
206
+ results.push(`Pressed keyboard key [${action.key || 'Enter'}]`);
207
+ }
208
+ else if (action.type === 'scroll') {
209
+ await activePage.evaluate(() => {
210
+ window.scrollBy({ top: window.innerHeight * 0.75, behavior: 'smooth' });
211
+ });
212
+ results.push(`Scrolled page smoothly`);
213
+ }
214
+ else if (action.type === 'waitFor') {
215
+ await activePage.waitForSelector(action.selector, { timeout: action.timeoutMs || 10000 });
216
+ results.push(`Element ${action.selector} appeared`);
217
+ }
218
+ else if (action.type === 'scrape') {
219
+ // Phase 1 AGI Upgrade: AXTree Mapper injection
220
+ const mappedMarkdown = await activePage.evaluate(() => {
221
+ let idx = 1;
222
+ let lines = [];
223
+ const elements = document.querySelectorAll('a, button, input, textarea, [role="button"], select');
224
+
225
+ elements.forEach(el => {
226
+ // Ignore hidden elements
227
+ const style = window.getComputedStyle(el);
228
+ if (style.display === 'none' || style.visibility === 'hidden' || el.offsetWidth === 0) return;
229
+
230
+ let label = el.innerText || el.value || el.placeholder || el.getAttribute('aria-label') || el.name || 'unlabeled';
231
+ label = label.trim().replace(/\n/g, ' ');
232
+ if (!label) return; // Skip empty elements
233
+
234
+ const tagName = el.tagName.toLowerCase();
235
+ lines.push(`[${idx}] ${tagName}: ${label}`);
236
+
237
+ el.setAttribute('data-causyne-id', idx.toString());
238
+ idx++;
239
+ });
240
+ return lines.join('\n');
241
+ });
242
+
243
+ results.push(`Scraped DOM Map:\n${mappedMarkdown}`);
244
+ }
245
+ }
246
+
247
+ return { success: true, log: results };
248
+ } catch (e) {
249
+ return { success: false, error: e.message };
250
+ }
251
+ }
252
+ };
@@ -0,0 +1,64 @@
1
+ import os from 'os';
2
+ import fs from 'fs/promises';
3
+
4
+ export const checkSystemSpecs = {
5
+ schema: {
6
+ name: 'check_system_specs',
7
+ description: 'Check the hardware specifications of the local machine (RAM, CPU, OS, Disk Space) to evaluate if it can run heavyweight local AI models.',
8
+ parameters: {
9
+ type: 'object',
10
+ properties: {},
11
+ required: []
12
+ }
13
+ },
14
+
15
+ execute: async (args, sandbox) => {
16
+ try {
17
+ const totalMemMB = Math.round(os.totalmem() / 1024 / 1024);
18
+ let freeMemMB = Math.round(os.freemem() / 1024 / 1024);
19
+ const cpuCores = os.cpus().length;
20
+ const cpuModel = os.cpus()[0]?.model || 'Unknown CPU';
21
+ const platform = os.platform();
22
+ const arch = os.arch();
23
+
24
+ // Phase 1 AGI Upgrade: Native Memory Math (macOS Approximation without shell)
25
+ if (platform === 'darwin') {
26
+ // os.freemem() on Mac only shows "free", not "inactive" memory.
27
+ // We use a safe heuristic based on total memory minus an active buffer baseline.
28
+ // This avoids brittle shell execution (`vm_stat`).
29
+ const usedApproximation = totalMemMB - freeMemMB;
30
+ const inactiveApproximation = usedApproximation * 0.4; // rough estimate of inactive
31
+ freeMemMB = Math.round(freeMemMB + inactiveApproximation);
32
+ }
33
+
34
+ // Phase 1 AGI Upgrade: Native Disk Math
35
+ let diskSpace = "Unknown";
36
+ try {
37
+ const checkPath = (sandbox && sandbox.workspacePaths && sandbox.workspacePaths.length > 0)
38
+ ? sandbox.workspacePaths[0]
39
+ : process.cwd();
40
+
41
+ const stats = await fs.statfs(checkPath);
42
+ // bavail (available blocks) * bsize (block size)
43
+ const freeBytes = stats.bavail * stats.bsize;
44
+ diskSpace = Math.round(freeBytes / 1024 / 1024 / 1024) + ' GB available';
45
+ } catch (e) {
46
+ diskSpace = "Could not verify disk space: " + e.message;
47
+ }
48
+
49
+ return {
50
+ success: true,
51
+ totalRamMB: totalMemMB,
52
+ freeRamMB: freeMemMB,
53
+ cpuCores: cpuCores,
54
+ cpuModel: cpuModel,
55
+ platform: platform,
56
+ architecture: arch,
57
+ diskSpace: diskSpace,
58
+ advice: freeMemMB < 4000 ? "WARNING: Less than 4GB RAM available. Insufficient for Stage 2 (Local AI Models). Please transition directly to Stage 3 (Pollinations Mode D), Stage 4 (Web Search), or Stage 5 (Procedural Mode A)." : "OK: Sufficient memory for Stage 2 (Local AI Models Mode B/C) execution."
59
+ };
60
+ } catch (err) {
61
+ return { success: false, error: err.message };
62
+ }
63
+ }
64
+ };
@@ -0,0 +1,36 @@
1
+ import { ensureCapability, loadCapabilityPackage } from '../provisioning/capabilityProvisioner.js';
2
+ export const clipboard = {
3
+ schema: {
4
+ name: 'clipboard',
5
+ description: 'Read or write to the system clipboard securely without shell execution.',
6
+ parameters: {
7
+ type: 'object',
8
+ properties: {
9
+ action: { type: 'string', enum: ['read', 'write'], description: 'Action to perform' },
10
+ text: { type: 'string', description: 'Text to write to the clipboard (required if action is write)' }
11
+ },
12
+ required: ['action']
13
+ }
14
+ },
15
+ async execute(args) {
16
+ try {
17
+ const capability = await ensureCapability('desktop');
18
+ if (!capability.ok) return { success: false, error: capability.reason, requiresConsent: capability.requiresConsent };
19
+ const clipboardy = await loadCapabilityPackage('clipboardy');
20
+
21
+ if (args.action === 'read') {
22
+ const content = await clipboardy.read();
23
+ return { success: true, output: content };
24
+ }
25
+ else if (args.action === 'write') {
26
+ if (!args.text) {
27
+ return { success: false, error: 'Text argument is required for write action' };
28
+ }
29
+ await clipboardy.write(args.text);
30
+ return { success: true, output: `Successfully wrote ${Buffer.byteLength(args.text, 'utf8')} bytes to OS Clipboard.` };
31
+ }
32
+ } catch (error) {
33
+ return { success: false, error: `Clipboard action failed: ${error.message}` };
34
+ }
35
+ }
36
+ };
@@ -0,0 +1,101 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import * as acorn from 'acorn';
4
+ import * as walk from 'acorn-walk';
5
+
6
+ export const codeSearchAst = {
7
+ schema: {
8
+ name: 'code_search_ast',
9
+ description: "Search for function, class, and variable definitions in a file using AST (Abstract Syntax Tree) without loading the entire file body. Ideal for understanding large codebases without eating up context window tokens.",
10
+ parameters: {
11
+ type: "object",
12
+ properties: {
13
+ targetFile: {
14
+ type: "string",
15
+ description: "Absolute path to the file to parse."
16
+ }
17
+ },
18
+ required: ["targetFile"]
19
+ }
20
+ },
21
+
22
+ // Named execute() like every other tool — as run() it was registered but the
23
+ // agent loop's toolRegistry[name].execute lookup could never reach it.
24
+ async execute({ targetFile }) {
25
+ if (!fs.existsSync(targetFile)) {
26
+ return `Error: File not found at ${targetFile}`;
27
+ }
28
+
29
+ try {
30
+ const code = fs.readFileSync(targetFile, 'utf8');
31
+ const ext = path.extname(targetFile).toLowerCase();
32
+ let output = `File: ${targetFile}\nStructure Summary:\n\n`;
33
+
34
+ if (ext === '.js' || ext === '.ts' || ext === '.jsx' || ext === '.tsx') {
35
+ // Use Acorn for deep, accurate JS/TS parsing
36
+ // We configure acorn to allow modern syntax and jsx/ts (tolerant mode via loose/default)
37
+ // Note: acorn natively only supports standard JS. For TS/JSX, we use standard regex fallback if acorn fails.
38
+ try {
39
+ const ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
40
+ const structures = [];
41
+
42
+ walk.simple(ast, {
43
+ FunctionDeclaration(node) {
44
+ const name = node.id ? node.id.name : 'anonymous';
45
+ structures.push(`Function: ${name}(...) [Line: ${node.loc ? node.loc.start.line : 'unknown'}]`);
46
+ },
47
+ ClassDeclaration(node) {
48
+ const name = node.id ? node.id.name : 'anonymous';
49
+ structures.push(`Class: ${name} [Line: ${node.loc ? node.loc.start.line : 'unknown'}]`);
50
+ },
51
+ VariableDeclarator(node) {
52
+ if (node.id && node.id.name && node.init) {
53
+ if (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression') {
54
+ structures.push(`Arrow/Var Function: ${node.id.name} [Line: ${node.loc ? node.loc.start.line : 'unknown'}]`);
55
+ } else if (node.id.name.toUpperCase() === node.id.name) { // Detect constants
56
+ structures.push(`Constant: ${node.id.name}`);
57
+ }
58
+ }
59
+ }
60
+ });
61
+
62
+ if (structures.length === 0) {
63
+ output += "No major functions or classes found via AST.\n";
64
+ } else {
65
+ output += structures.join('\n');
66
+ }
67
+ return output;
68
+
69
+ } catch (acornError) {
70
+ // If acorn fails (e.g., on TS or JSX syntax), fallback to regex
71
+ output += `[AST Parse Warning: ${acornError.message}. Falling back to Regex Pseudo-AST]\n`;
72
+ }
73
+ }
74
+
75
+ // Regex Pseudo-AST Fallback (for Python, Java, Go, or TS/JSX if acorn fails)
76
+ const lines = code.split('\n');
77
+ const results = [];
78
+ const functionRegex = /^(?:export\s+)?(?:async\s+)?(?:function\s+|class\s+|const\s+\w+\s*=\s*(?:async\s*)?(?:\([^)]*\)|[^=]+)\s*=>|def\s+|class\s+|func\s+|public\s+class\s+|public\s+static\s+|type\s+|interface\s+)/;
79
+
80
+ for (let i = 0; i < lines.length; i++) {
81
+ const line = lines[i].trim();
82
+ if (functionRegex.test(line)) {
83
+ // Clean up the line to just show the signature (remove opening brace if present)
84
+ let signature = line.replace(/\{$/, '').trim();
85
+ results.push(`Line ${i + 1}: ${signature}`);
86
+ }
87
+ }
88
+
89
+ if (results.length === 0) {
90
+ output += "No structural definitions found.\n";
91
+ } else {
92
+ output += results.join('\n');
93
+ }
94
+
95
+ return output;
96
+
97
+ } catch (e) {
98
+ return `Error analyzing file: ${e.message}`;
99
+ }
100
+ }
101
+ };
@@ -0,0 +1,230 @@
1
+ import fs from 'fs/promises';
2
+ import fsSync from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { execFile } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import { causyneHome } from '../utils/paths.js';
8
+
9
+ const execFileAsync = promisify(execFile);
10
+
11
+ // Helper: Check system ripgrep binary availability
12
+ function getRipgrepBinary() {
13
+ const isWin = process.platform === 'win32';
14
+ const candidates = isWin
15
+ ? ['C:\\Program Files\\ripgrep\\rg.exe', 'rg.exe']
16
+ : ['/opt/homebrew/bin/rg', '/usr/local/bin/rg', '/usr/bin/rg', 'rg'];
17
+
18
+ for (const bin of candidates) {
19
+ try {
20
+ if (fsSync.existsSync(bin)) return bin;
21
+ } catch (e) {}
22
+ }
23
+ return null;
24
+ }
25
+
26
+ // Helper: V5 Cortex SQLite Cache (non-blocking schema init)
27
+ function getCortexDb() {
28
+ try {
29
+ const dbPath = path.join(causyneHome(), 'cortex.db');
30
+ if (!fsSync.existsSync(path.dirname(dbPath))) {
31
+ fsSync.mkdirSync(path.dirname(dbPath), { recursive: true });
32
+ }
33
+ // Lazy check if better-sqlite3 is available in dependencies
34
+ const Database = require('better-sqlite3');
35
+ const db = new Database(dbPath, { timeout: 2000 });
36
+ db.exec(`
37
+ CREATE TABLE IF NOT EXISTS code_search_cache (
38
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ query TEXT,
40
+ search_path TEXT,
41
+ results_json TEXT,
42
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
43
+ );
44
+ CREATE INDEX IF NOT EXISTS idx_code_search_query ON code_search_cache(query, search_path);
45
+ `);
46
+ return db;
47
+ } catch (e) {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ export const codeSearcher = {
53
+ schema: {
54
+ name: 'code.search_ripgrep',
55
+ description: 'Blazing fast regex, string, and AST symbol search across entire codebases using dual-engine ripgrep + fast Node stream and V5 Cortex SQLite caching. Surpasses standard CLI glob search.',
56
+ parameters: {
57
+ type: 'object',
58
+ properties: {
59
+ query: { type: 'string', description: 'The exact string, symbol, or regular expression pattern to look for across files.' },
60
+ searchPath: { type: 'string', description: 'Absolute root directory or file path to search. Defaults to current working directory.' },
61
+ isRegex: { type: 'boolean', description: 'If true, treats query as a regular expression pattern. If false, treats as literal string.' },
62
+ caseInsensitive: { type: 'boolean', description: 'If true, performs case-insensitive search (default true).' },
63
+ filePattern: { type: 'string', description: 'Optional glob filter (e.g. "*.js", "*.py", "*.kt", "*.ts") to restrict search to specific file extensions.' },
64
+ astMode: { type: 'boolean', description: 'If true, filters results specifically for AST symbol definitions (classes, functions, interfaces, structs, defs).' }
65
+ },
66
+ required: ['query']
67
+ }
68
+ },
69
+
70
+ async execute(args) {
71
+ try {
72
+ const query = args.query;
73
+ if (!query) return { success: false, error: 'Query parameter is required.' };
74
+
75
+ const targetDir = (args.searchPath || process.cwd()).replace(/^~\//, os.homedir() + '/');
76
+ const isRegex = args.isRegex === true;
77
+ const caseInsensitive = args.caseInsensitive !== false; // default true
78
+ const filePattern = args.filePattern || '*';
79
+ const astMode = args.astMode === true;
80
+
81
+ // Check V5 Cortex SQLite Cache first
82
+ let db = null;
83
+ try {
84
+ db = getCortexDb();
85
+ if (db) {
86
+ const cacheKey = `${query}_${isRegex}_${caseInsensitive}_${filePattern}_${astMode}`;
87
+ const row = db.prepare('SELECT results_json FROM code_search_cache WHERE query = ? AND search_path = ? AND timestamp >= datetime("now", "-10 minutes") LIMIT 1').get(cacheKey, targetDir);
88
+ if (row && row.results_json) {
89
+ const cachedResults = JSON.parse(row.results_json);
90
+ return {
91
+ success: true,
92
+ output: JSON.stringify({
93
+ engine: 'Cortex Neural Cache (0ms)',
94
+ query: query,
95
+ total_matches: cachedResults.length,
96
+ matches: cachedResults
97
+ }, null, 2)
98
+ };
99
+ }
100
+ }
101
+ } catch (cacheErr) {}
102
+
103
+ const matches = [];
104
+ const MAX_MATCHES = 100;
105
+ const rgBin = getRipgrepBinary();
106
+
107
+ // Dual-Engine 1: Ripgrep Binary
108
+ if (rgBin) {
109
+ const rgArgs = ['-n', '--no-heading', `--max-count=${MAX_MATCHES}`];
110
+ if (caseInsensitive) rgArgs.push('-i');
111
+ if (!isRegex) rgArgs.push('-F');
112
+ if (filePattern && filePattern !== '*') rgArgs.push('-g', filePattern);
113
+
114
+ // If AST mode, build an AST regex around the symbol
115
+ let finalQuery = query;
116
+ if (astMode && !isRegex) {
117
+ rgArgs.push('-e');
118
+ finalQuery = `(class|function|def|interface|struct|const|let|var|type|func|module)\\s+[^\\s(]*${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`;
119
+ } else {
120
+ rgArgs.push('-e', finalQuery);
121
+ }
122
+ rgArgs.push(targetDir);
123
+
124
+ try {
125
+ const { stdout } = await execFileAsync(rgBin, rgArgs, { maxBuffer: 10 * 1024 * 1024 });
126
+ const lines = stdout.split('\n').filter(Boolean);
127
+ for (const line of lines) {
128
+ if (matches.length >= MAX_MATCHES) break;
129
+ const parts = line.split(':');
130
+ if (parts.length >= 3) {
131
+ const file = parts[0];
132
+ const lineNo = parseInt(parts[1], 10) || 0;
133
+ const content = parts.slice(2).join(':').trim();
134
+ matches.push({ File: file, LineNumber: lineNo, LineContent: content });
135
+ }
136
+ }
137
+ } catch (rgErr) {
138
+ // If exit code is 1, ripgrep simply found no matches. Otherwise fall through to Node stream.
139
+ if (rgErr.code !== 1 && rgErr.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
140
+ // Fallback to async Node stream
141
+ }
142
+ }
143
+ }
144
+
145
+ // Dual-Engine 2: Fast Asynchronous Node Stream Fallback (if rg returned empty or not installed)
146
+ if (matches.length === 0 && (!rgBin || !fsSync.existsSync(rgBin))) {
147
+ const EXCLUDE_DIRS = new Set(['node_modules', '.git', '.gradle', 'build', 'dist', '.idea', '.causyne_trash']);
148
+
149
+ let patternRegExp;
150
+ try {
151
+ let pat = isRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
152
+ if (astMode && !isRegex) {
153
+ pat = `(class|function|def|interface|struct|const|let|var|type|func|module)\\s+[^\\s(]*${pat}`;
154
+ }
155
+ patternRegExp = new RegExp(pat, caseInsensitive ? 'i' : '');
156
+ } catch (regErr) {
157
+ return { success: false, error: `Invalid regex pattern: ${regErr.message}` };
158
+ }
159
+
160
+ const extFilter = (filePattern && filePattern !== '*' && filePattern.startsWith('*.'))
161
+ ? filePattern.slice(1).toLowerCase()
162
+ : null;
163
+
164
+ async function walkSearch(dir) {
165
+ if (matches.length >= MAX_MATCHES) return;
166
+ let dirents;
167
+ try {
168
+ dirents = await fs.readdir(dir, { withFileTypes: true });
169
+ } catch (e) {
170
+ return;
171
+ }
172
+
173
+ const promises = [];
174
+ for (const dirent of dirents) {
175
+ if (matches.length >= MAX_MATCHES) break;
176
+ if (dirent.isDirectory()) {
177
+ if (!EXCLUDE_DIRS.has(dirent.name)) {
178
+ promises.push(walkSearch(path.join(dir, dirent.name)));
179
+ }
180
+ } else if (dirent.isFile()) {
181
+ if (extFilter && !dirent.name.toLowerCase().endsWith(extFilter)) continue;
182
+
183
+ promises.push(
184
+ fs.readFile(path.join(dir, dirent.name), 'utf8')
185
+ .then(content => {
186
+ const lines = content.split('\n');
187
+ for (let i = 0; i < lines.length; i++) {
188
+ if (matches.length >= MAX_MATCHES) break;
189
+ if (patternRegExp.test(lines[i])) {
190
+ matches.push({
191
+ File: path.join(dir, dirent.name),
192
+ LineNumber: i + 1,
193
+ LineContent: lines[i].trim()
194
+ });
195
+ }
196
+ }
197
+ })
198
+ .catch(() => {}) // Ignore binary/unreadable files
199
+ );
200
+ }
201
+ }
202
+ await Promise.all(promises);
203
+ }
204
+
205
+ await walkSearch(targetDir);
206
+ }
207
+
208
+ // Cache matches in Cortex SQLite if db active
209
+ if (db && matches.length > 0) {
210
+ try {
211
+ const cacheKey = `${query}_${isRegex}_${caseInsensitive}_${filePattern}_${astMode}`;
212
+ db.prepare('INSERT INTO code_search_cache (query, search_path, results_json) VALUES (?, ?, ?)')
213
+ .run(cacheKey, targetDir, JSON.stringify(matches));
214
+ } catch (cacheStoreErr) {}
215
+ }
216
+
217
+ return {
218
+ success: true,
219
+ output: JSON.stringify({
220
+ engine: rgBin ? `Ripgrep Binary (${rgBin})` : 'Node Async Stream Engine',
221
+ query: query,
222
+ total_matches: matches.length,
223
+ matches: matches.length > 0 ? matches : "No matches found in target codebase."
224
+ }, null, 2)
225
+ };
226
+ } catch (e) {
227
+ return { success: false, error: e.message };
228
+ }
229
+ }
230
+ };