elasticdash-sdk 0.2.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 (349) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +775 -0
  3. package/dist/browser-ui.d.ts +43 -0
  4. package/dist/browser-ui.d.ts.map +1 -0
  5. package/dist/browser-ui.js +246 -0
  6. package/dist/browser-ui.js.map +1 -0
  7. package/dist/capture/event.d.ts +33 -0
  8. package/dist/capture/event.d.ts.map +1 -0
  9. package/dist/capture/event.js +2 -0
  10. package/dist/capture/event.js.map +1 -0
  11. package/dist/capture/index.d.ts +4 -0
  12. package/dist/capture/index.d.ts.map +1 -0
  13. package/dist/capture/index.js +4 -0
  14. package/dist/capture/index.js.map +1 -0
  15. package/dist/capture/recorder.d.ts +24 -0
  16. package/dist/capture/recorder.d.ts.map +1 -0
  17. package/dist/capture/recorder.js +46 -0
  18. package/dist/capture/recorder.js.map +1 -0
  19. package/dist/capture/replay.d.ts +20 -0
  20. package/dist/capture/replay.d.ts.map +1 -0
  21. package/dist/capture/replay.js +47 -0
  22. package/dist/capture/replay.js.map +1 -0
  23. package/dist/ci/api-client.d.ts +38 -0
  24. package/dist/ci/api-client.d.ts.map +1 -0
  25. package/dist/ci/api-client.js +96 -0
  26. package/dist/ci/api-client.js.map +1 -0
  27. package/dist/ci/benchmark.d.ts +33 -0
  28. package/dist/ci/benchmark.d.ts.map +1 -0
  29. package/dist/ci/benchmark.js +213 -0
  30. package/dist/ci/benchmark.js.map +1 -0
  31. package/dist/ci/ed-runner.d.ts +48 -0
  32. package/dist/ci/ed-runner.d.ts.map +1 -0
  33. package/dist/ci/ed-runner.js +260 -0
  34. package/dist/ci/ed-runner.js.map +1 -0
  35. package/dist/ci/executor.d.ts +13 -0
  36. package/dist/ci/executor.d.ts.map +1 -0
  37. package/dist/ci/executor.js +542 -0
  38. package/dist/ci/executor.js.map +1 -0
  39. package/dist/ci/git-info.d.ts +17 -0
  40. package/dist/ci/git-info.d.ts.map +1 -0
  41. package/dist/ci/git-info.js +102 -0
  42. package/dist/ci/git-info.js.map +1 -0
  43. package/dist/ci/index.d.ts +6 -0
  44. package/dist/ci/index.d.ts.map +1 -0
  45. package/dist/ci/index.js +4 -0
  46. package/dist/ci/index.js.map +1 -0
  47. package/dist/ci/measurement.d.ts +9 -0
  48. package/dist/ci/measurement.d.ts.map +1 -0
  49. package/dist/ci/measurement.js +15 -0
  50. package/dist/ci/measurement.js.map +1 -0
  51. package/dist/ci/replay.d.ts +31 -0
  52. package/dist/ci/replay.d.ts.map +1 -0
  53. package/dist/ci/replay.js +96 -0
  54. package/dist/ci/replay.js.map +1 -0
  55. package/dist/ci/reporters/default.d.ts +8 -0
  56. package/dist/ci/reporters/default.d.ts.map +1 -0
  57. package/dist/ci/reporters/default.js +46 -0
  58. package/dist/ci/reporters/default.js.map +1 -0
  59. package/dist/ci/reporters/index.d.ts +8 -0
  60. package/dist/ci/reporters/index.d.ts.map +1 -0
  61. package/dist/ci/reporters/index.js +14 -0
  62. package/dist/ci/reporters/index.js.map +1 -0
  63. package/dist/ci/reporters/json.d.ts +8 -0
  64. package/dist/ci/reporters/json.d.ts.map +1 -0
  65. package/dist/ci/reporters/json.js +14 -0
  66. package/dist/ci/reporters/json.js.map +1 -0
  67. package/dist/ci/reporters/junit.d.ts +8 -0
  68. package/dist/ci/reporters/junit.d.ts.map +1 -0
  69. package/dist/ci/reporters/junit.js +48 -0
  70. package/dist/ci/reporters/junit.js.map +1 -0
  71. package/dist/ci/runner.d.ts +3 -0
  72. package/dist/ci/runner.d.ts.map +1 -0
  73. package/dist/ci/runner.js +187 -0
  74. package/dist/ci/runner.js.map +1 -0
  75. package/dist/ci/test-discovery.d.ts +5 -0
  76. package/dist/ci/test-discovery.d.ts.map +1 -0
  77. package/dist/ci/test-discovery.js +11 -0
  78. package/dist/ci/test-discovery.js.map +1 -0
  79. package/dist/ci/test-loader.d.ts +19 -0
  80. package/dist/ci/test-loader.d.ts.map +1 -0
  81. package/dist/ci/test-loader.js +149 -0
  82. package/dist/ci/test-loader.js.map +1 -0
  83. package/dist/ci/test-registry.d.ts +42 -0
  84. package/dist/ci/test-registry.d.ts.map +1 -0
  85. package/dist/ci/test-registry.js +18 -0
  86. package/dist/ci/test-registry.js.map +1 -0
  87. package/dist/ci/trace-schema.d.ts +30 -0
  88. package/dist/ci/trace-schema.d.ts.map +1 -0
  89. package/dist/ci/trace-schema.js +66 -0
  90. package/dist/ci/trace-schema.js.map +1 -0
  91. package/dist/ci/trace-writer.d.ts +16 -0
  92. package/dist/ci/trace-writer.d.ts.map +1 -0
  93. package/dist/ci/trace-writer.js +108 -0
  94. package/dist/ci/trace-writer.js.map +1 -0
  95. package/dist/ci/types.d.ts +108 -0
  96. package/dist/ci/types.d.ts.map +1 -0
  97. package/dist/ci/types.js +3 -0
  98. package/dist/ci/types.js.map +1 -0
  99. package/dist/ci/upload-client.d.ts +74 -0
  100. package/dist/ci/upload-client.d.ts.map +1 -0
  101. package/dist/ci/upload-client.js +195 -0
  102. package/dist/ci/upload-client.js.map +1 -0
  103. package/dist/cli.d.ts +3 -0
  104. package/dist/cli.d.ts.map +1 -0
  105. package/dist/cli.js +716 -0
  106. package/dist/cli.js.map +1 -0
  107. package/dist/core/agent-state.d.ts +47 -0
  108. package/dist/core/agent-state.d.ts.map +1 -0
  109. package/dist/core/agent-state.js +137 -0
  110. package/dist/core/agent-state.js.map +1 -0
  111. package/dist/core/judge-utils.d.ts +22 -0
  112. package/dist/core/judge-utils.d.ts.map +1 -0
  113. package/dist/core/judge-utils.js +211 -0
  114. package/dist/core/judge-utils.js.map +1 -0
  115. package/dist/core/registry.d.ts +28 -0
  116. package/dist/core/registry.d.ts.map +1 -0
  117. package/dist/core/registry.js +52 -0
  118. package/dist/core/registry.js.map +1 -0
  119. package/dist/dashboard-server.d.ts +65 -0
  120. package/dist/dashboard-server.d.ts.map +1 -0
  121. package/dist/dashboard-server.js +3940 -0
  122. package/dist/dashboard-server.js.map +1 -0
  123. package/dist/execution/tool-runner.d.ts +26 -0
  124. package/dist/execution/tool-runner.d.ts.map +1 -0
  125. package/dist/execution/tool-runner.js +316 -0
  126. package/dist/execution/tool-runner.js.map +1 -0
  127. package/dist/html/dashboard.html +2218 -0
  128. package/dist/http.d.ts +14 -0
  129. package/dist/http.d.ts.map +1 -0
  130. package/dist/http.js +13 -0
  131. package/dist/http.js.map +1 -0
  132. package/dist/index.cjs +8102 -0
  133. package/dist/index.d.ts +61 -0
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +67 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/interceptors/ai-interceptor.d.ts +26 -0
  138. package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
  139. package/dist/interceptors/ai-interceptor.js +756 -0
  140. package/dist/interceptors/ai-interceptor.js.map +1 -0
  141. package/dist/interceptors/db-auto.d.ts +8 -0
  142. package/dist/interceptors/db-auto.d.ts.map +1 -0
  143. package/dist/interceptors/db-auto.js +217 -0
  144. package/dist/interceptors/db-auto.js.map +1 -0
  145. package/dist/interceptors/db.d.ts +23 -0
  146. package/dist/interceptors/db.d.ts.map +1 -0
  147. package/dist/interceptors/db.js +137 -0
  148. package/dist/interceptors/db.js.map +1 -0
  149. package/dist/interceptors/http.d.ts +28 -0
  150. package/dist/interceptors/http.d.ts.map +1 -0
  151. package/dist/interceptors/http.js +356 -0
  152. package/dist/interceptors/http.js.map +1 -0
  153. package/dist/interceptors/side-effects.d.ts +7 -0
  154. package/dist/interceptors/side-effects.d.ts.map +1 -0
  155. package/dist/interceptors/side-effects.js +72 -0
  156. package/dist/interceptors/side-effects.js.map +1 -0
  157. package/dist/interceptors/telemetry-push.d.ts +142 -0
  158. package/dist/interceptors/telemetry-push.d.ts.map +1 -0
  159. package/dist/interceptors/telemetry-push.js +463 -0
  160. package/dist/interceptors/telemetry-push.js.map +1 -0
  161. package/dist/interceptors/tool.d.ts +2 -0
  162. package/dist/interceptors/tool.d.ts.map +1 -0
  163. package/dist/interceptors/tool.js +274 -0
  164. package/dist/interceptors/tool.js.map +1 -0
  165. package/dist/interceptors/workflow-ai.d.ts +5 -0
  166. package/dist/interceptors/workflow-ai.d.ts.map +1 -0
  167. package/dist/interceptors/workflow-ai.js +382 -0
  168. package/dist/interceptors/workflow-ai.js.map +1 -0
  169. package/dist/internals/conditional-recorder.d.ts +21 -0
  170. package/dist/internals/conditional-recorder.d.ts.map +1 -0
  171. package/dist/internals/conditional-recorder.js +54 -0
  172. package/dist/internals/conditional-recorder.js.map +1 -0
  173. package/dist/internals/mock-resolver.d.ts +146 -0
  174. package/dist/internals/mock-resolver.d.ts.map +1 -0
  175. package/dist/internals/mock-resolver.js +427 -0
  176. package/dist/internals/mock-resolver.js.map +1 -0
  177. package/dist/matchers/index.d.ts +96 -0
  178. package/dist/matchers/index.d.ts.map +1 -0
  179. package/dist/matchers/index.js +668 -0
  180. package/dist/matchers/index.js.map +1 -0
  181. package/dist/observability.d.ts +82 -0
  182. package/dist/observability.d.ts.map +1 -0
  183. package/dist/observability.js +471 -0
  184. package/dist/observability.js.map +1 -0
  185. package/dist/portal-executor.d.ts +30 -0
  186. package/dist/portal-executor.d.ts.map +1 -0
  187. package/dist/portal-executor.js +324 -0
  188. package/dist/portal-executor.js.map +1 -0
  189. package/dist/portal-server.d.ts +3 -0
  190. package/dist/portal-server.d.ts.map +1 -0
  191. package/dist/portal-server.js +279 -0
  192. package/dist/portal-server.js.map +1 -0
  193. package/dist/proxy/llm-capture.d.ts +14 -0
  194. package/dist/proxy/llm-capture.d.ts.map +1 -0
  195. package/dist/proxy/llm-capture.js +264 -0
  196. package/dist/proxy/llm-capture.js.map +1 -0
  197. package/dist/reporter.d.ts +3 -0
  198. package/dist/reporter.d.ts.map +1 -0
  199. package/dist/reporter.js +72 -0
  200. package/dist/reporter.js.map +1 -0
  201. package/dist/runWorkflowSubprocess.d.ts +14 -0
  202. package/dist/runWorkflowSubprocess.d.ts.map +1 -0
  203. package/dist/runWorkflowSubprocess.js +66 -0
  204. package/dist/runWorkflowSubprocess.js.map +1 -0
  205. package/dist/runner.d.ts +16 -0
  206. package/dist/runner.d.ts.map +1 -0
  207. package/dist/runner.js +138 -0
  208. package/dist/runner.js.map +1 -0
  209. package/dist/socket-connector.d.ts +22 -0
  210. package/dist/socket-connector.d.ts.map +1 -0
  211. package/dist/socket-connector.js +104 -0
  212. package/dist/socket-connector.js.map +1 -0
  213. package/dist/telemetry-batcher.d.ts +56 -0
  214. package/dist/telemetry-batcher.d.ts.map +1 -0
  215. package/dist/telemetry-batcher.js +143 -0
  216. package/dist/telemetry-batcher.js.map +1 -0
  217. package/dist/test-setup.d.ts +12 -0
  218. package/dist/test-setup.d.ts.map +1 -0
  219. package/dist/test-setup.js +13 -0
  220. package/dist/test-setup.js.map +1 -0
  221. package/dist/tool-registry.d.ts +31 -0
  222. package/dist/tool-registry.d.ts.map +1 -0
  223. package/dist/tool-registry.js +73 -0
  224. package/dist/tool-registry.js.map +1 -0
  225. package/dist/tool-runner-worker.d.ts +2 -0
  226. package/dist/tool-runner-worker.d.ts.map +1 -0
  227. package/dist/tool-runner-worker.js +215 -0
  228. package/dist/tool-runner-worker.js.map +1 -0
  229. package/dist/trace-adapter/context.d.ts +72 -0
  230. package/dist/trace-adapter/context.d.ts.map +1 -0
  231. package/dist/trace-adapter/context.js +80 -0
  232. package/dist/trace-adapter/context.js.map +1 -0
  233. package/dist/tracing.d.ts +2 -0
  234. package/dist/tracing.d.ts.map +1 -0
  235. package/dist/tracing.js +59 -0
  236. package/dist/tracing.js.map +1 -0
  237. package/dist/trigger-executor.d.ts +12 -0
  238. package/dist/trigger-executor.d.ts.map +1 -0
  239. package/dist/trigger-executor.js +130 -0
  240. package/dist/trigger-executor.js.map +1 -0
  241. package/dist/types/portal.d.ts +76 -0
  242. package/dist/types/portal.d.ts.map +1 -0
  243. package/dist/types/portal.js +2 -0
  244. package/dist/types/portal.js.map +1 -0
  245. package/dist/utils/debug.d.ts +3 -0
  246. package/dist/utils/debug.d.ts.map +1 -0
  247. package/dist/utils/debug.js +8 -0
  248. package/dist/utils/debug.js.map +1 -0
  249. package/dist/utils/license-error.d.ts +23 -0
  250. package/dist/utils/license-error.d.ts.map +1 -0
  251. package/dist/utils/license-error.js +42 -0
  252. package/dist/utils/license-error.js.map +1 -0
  253. package/dist/utils/redact.d.ts +7 -0
  254. package/dist/utils/redact.d.ts.map +1 -0
  255. package/dist/utils/redact.js +26 -0
  256. package/dist/utils/redact.js.map +1 -0
  257. package/dist/workflow-runner-worker.d.ts +2 -0
  258. package/dist/workflow-runner-worker.d.ts.map +1 -0
  259. package/dist/workflow-runner-worker.js +329 -0
  260. package/dist/workflow-runner-worker.js.map +1 -0
  261. package/dist/workflow-runner.d.ts +14 -0
  262. package/dist/workflow-runner.d.ts.map +1 -0
  263. package/dist/workflow-runner.js +34 -0
  264. package/dist/workflow-runner.js.map +1 -0
  265. package/docs/agent-coding-instructions.md +138 -0
  266. package/docs/agent-integration-guide.md +564 -0
  267. package/docs/agents.md +140 -0
  268. package/docs/dashboard.md +394 -0
  269. package/docs/deno.md +69 -0
  270. package/docs/instrumentation.md +424 -0
  271. package/docs/langfuse-trace-structure.md +145 -0
  272. package/docs/matchers.md +173 -0
  273. package/docs/observability_contract.md +192 -0
  274. package/docs/observability_mode.md +195 -0
  275. package/docs/quickstart.md +621 -0
  276. package/docs/security-compliance.md +566 -0
  277. package/docs/test-writing-guidelines.md +444 -0
  278. package/docs/tools.md +165 -0
  279. package/docs/workflow-modes.md +253 -0
  280. package/package.json +76 -0
  281. package/src/browser-ui.ts +281 -0
  282. package/src/capture/event.ts +30 -0
  283. package/src/capture/index.ts +3 -0
  284. package/src/capture/recorder.ts +62 -0
  285. package/src/capture/replay.ts +55 -0
  286. package/src/ci/api-client.ts +136 -0
  287. package/src/ci/benchmark.ts +257 -0
  288. package/src/ci/ed-runner.ts +351 -0
  289. package/src/ci/executor.ts +671 -0
  290. package/src/ci/git-info.ts +127 -0
  291. package/src/ci/index.ts +5 -0
  292. package/src/ci/measurement.ts +25 -0
  293. package/src/ci/replay.ts +127 -0
  294. package/src/ci/reporters/default.ts +50 -0
  295. package/src/ci/reporters/index.ts +21 -0
  296. package/src/ci/reporters/json.ts +18 -0
  297. package/src/ci/reporters/junit.ts +61 -0
  298. package/src/ci/runner.ts +208 -0
  299. package/src/ci/test-discovery.ts +16 -0
  300. package/src/ci/test-loader.ts +187 -0
  301. package/src/ci/test-registry.ts +62 -0
  302. package/src/ci/trace-schema.ts +96 -0
  303. package/src/ci/trace-writer.ts +107 -0
  304. package/src/ci/types.ts +115 -0
  305. package/src/ci/upload-client.ts +300 -0
  306. package/src/cli.ts +811 -0
  307. package/src/core/agent-state.ts +162 -0
  308. package/src/core/judge-utils.ts +232 -0
  309. package/src/core/registry.ts +92 -0
  310. package/src/dashboard-server.ts +2047 -0
  311. package/src/execution/tool-runner.ts +352 -0
  312. package/src/html/dashboard.html +2218 -0
  313. package/src/http.ts +13 -0
  314. package/src/index.ts +138 -0
  315. package/src/interceptors/ai-interceptor.ts +798 -0
  316. package/src/interceptors/db-auto.ts +243 -0
  317. package/src/interceptors/db.ts +156 -0
  318. package/src/interceptors/http.ts +393 -0
  319. package/src/interceptors/side-effects.ts +83 -0
  320. package/src/interceptors/telemetry-push.ts +537 -0
  321. package/src/interceptors/tool.ts +287 -0
  322. package/src/interceptors/workflow-ai.ts +419 -0
  323. package/src/internals/conditional-recorder.ts +63 -0
  324. package/src/internals/mock-resolver.ts +492 -0
  325. package/src/matchers/index.ts +824 -0
  326. package/src/observability.ts +501 -0
  327. package/src/portal-executor.ts +355 -0
  328. package/src/portal-server.ts +304 -0
  329. package/src/proxy/llm-capture.ts +301 -0
  330. package/src/reporter.ts +81 -0
  331. package/src/runWorkflowSubprocess.ts +74 -0
  332. package/src/runner.ts +178 -0
  333. package/src/socket-connector.ts +117 -0
  334. package/src/telemetry-batcher.ts +191 -0
  335. package/src/test-setup.ts +16 -0
  336. package/src/tool-registry.ts +94 -0
  337. package/src/tool-runner-worker.ts +244 -0
  338. package/src/trace-adapter/context.ts +156 -0
  339. package/src/tracing.ts +62 -0
  340. package/src/trigger-executor.ts +171 -0
  341. package/src/types/agent.d.ts +63 -0
  342. package/src/types/expect.d.ts +81 -0
  343. package/src/types/modules.d.ts +2 -0
  344. package/src/types/portal.ts +69 -0
  345. package/src/utils/debug.ts +8 -0
  346. package/src/utils/license-error.ts +43 -0
  347. package/src/utils/redact.ts +25 -0
  348. package/src/workflow-runner-worker.ts +386 -0
  349. package/src/workflow-runner.ts +58 -0
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Shared tool/AI execution helpers for portal and dashboard.
3
+ * These are extracted versions of the helpers in dashboard-server.ts
4
+ * to avoid importing the full dashboard server module.
5
+ */
6
+ import path from 'node:path'
7
+ import { existsSync, readFileSync } from 'node:fs'
8
+ import { spawn } from 'node:child_process'
9
+ import { pathToFileURL } from 'node:url'
10
+ import { debugLog } from '../utils/debug.js'
11
+
12
+ /**
13
+ * Resolve a worker script relative to the current module.
14
+ * Works in both ESM (import.meta.url is a valid file:// URL) and CJS
15
+ * (bundled as dist/index.cjs where import.meta is shimmed to {}).
16
+ */
17
+ function resolveWorkerScript(relativePath: string): string {
18
+ try {
19
+ return new URL(relativePath, import.meta.url).pathname
20
+ } catch {
21
+ // CJS fallback: in the bundled index.cjs, import.meta.url is undefined.
22
+ // The worker script is a separate file in the same dist/ directory as the
23
+ // bundle, so resolve its basename against __dirname.
24
+ const workerName = path.basename(relativePath)
25
+ const dir = typeof __dirname !== 'undefined' ? __dirname : process.cwd()
26
+ return path.resolve(dir, workerName)
27
+ }
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Types
32
+ // ---------------------------------------------------------------------------
33
+
34
+ export interface ToolInfo {
35
+ name: string
36
+ isAsync: boolean
37
+ signature: string
38
+ filePath: string
39
+ lineNumber?: number
40
+ sourceCode?: string
41
+ }
42
+
43
+ export interface RerunResult {
44
+ ok: boolean
45
+ currentOutput?: unknown
46
+ currentDurationMs?: number
47
+ currentUsage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number }
48
+ error?: string
49
+ }
50
+
51
+ interface ParsedExport {
52
+ name: string
53
+ isAsync: boolean
54
+ signature: string
55
+ filePath: string
56
+ lineNumber?: number
57
+ sourceCode?: string
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Runtime helpers
62
+ // ---------------------------------------------------------------------------
63
+
64
+ export function isDenoProject(dir: string): boolean {
65
+ return existsSync(path.join(dir, 'deno.json')) || existsSync(path.join(dir, 'deno.jsonc'))
66
+ }
67
+
68
+ export function resolveRuntimeModule(cwd: string, baseName: string): string | null {
69
+ for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {
70
+ const candidate = path.join(cwd, `${baseName}${ext}`)
71
+ if (existsSync(candidate)) return candidate
72
+ }
73
+ return null
74
+ }
75
+
76
+ function parseSignatureParams(signature?: string): string[] {
77
+ if (!signature) return []
78
+ const trimmed = signature.trim()
79
+ if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) return []
80
+ const body = trimmed.slice(1, -1).trim()
81
+ if (!body) return []
82
+ return body
83
+ .split(',')
84
+ .map(part => part.trim())
85
+ .filter(Boolean)
86
+ .map(part => part.replace(/^\.\.\./, '').split('=')[0].split(':')[0].replace(/\?/g, '').trim())
87
+ .filter(part => /^[$A-Z_][0-9A-Z_$]*$/i.test(part))
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Argument building
92
+ // ---------------------------------------------------------------------------
93
+
94
+ export function buildToolArgs(input: unknown, tool?: ToolInfo): unknown[] {
95
+ if (input === undefined) return []
96
+ if (Array.isArray(input)) return input
97
+ if (input && typeof input === 'object') {
98
+ const argObject = input as Record<string, unknown>
99
+ const paramNames = parseSignatureParams(tool?.signature)
100
+ if (paramNames.length > 0 && paramNames.every(name => Object.prototype.hasOwnProperty.call(argObject, name))) {
101
+ return paramNames.map(name => argObject[name])
102
+ }
103
+ return [input]
104
+ }
105
+ return [input]
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Subprocess execution
110
+ // ---------------------------------------------------------------------------
111
+
112
+ export function runToolInSubprocess(
113
+ toolsModulePath: string,
114
+ toolName: string,
115
+ args: unknown[],
116
+ frozenEvents?: unknown[],
117
+ ): Promise<RerunResult> {
118
+ return new Promise((resolve) => {
119
+ debugLog('[elasticdash portal] Spawning tool subprocess', { toolsModulePath, toolName, args, frozenEventsCount: frozenEvents?.length ?? 0 })
120
+ const startMs = Date.now()
121
+ const workerScript = resolveWorkerScript('../tool-runner-worker.js')
122
+ const projectDir = path.dirname(toolsModulePath)
123
+ const denoProject = isDenoProject(projectDir)
124
+
125
+ const nodeOptions = process.env.NODE_OPTIONS ?? ''
126
+ const tsxFlag = '--import tsx'
127
+ const childNodeOptions = nodeOptions.includes('tsx') ? nodeOptions : `${nodeOptions} ${tsxFlag}`.trim()
128
+ const childEnv = { ...process.env, NODE_OPTIONS: denoProject ? nodeOptions : childNodeOptions }
129
+
130
+ const runtime = denoProject ? 'deno' : process.execPath
131
+ const runtimeArgs = denoProject ? ['run', '--allow-all', workerScript] : [workerScript]
132
+ debugLog(`[elasticdash portal] Runtime for tool subprocess: ${runtime} ${runtimeArgs.join(' ')}`, { isDenoProject: denoProject })
133
+
134
+ const child = spawn(runtime, runtimeArgs, {
135
+ env: childEnv,
136
+ cwd: projectDir,
137
+ stdio: ['pipe', 'pipe', 'pipe'],
138
+ })
139
+
140
+ const RESULT_PREFIX = '__ELASTICDASH_RESULT__:'
141
+ let resultLine = ''
142
+ let stderr = ''
143
+
144
+ // Line-buffer stdout so that large result JSON lines split across multiple
145
+ // data events are reassembled before processing.
146
+ let stdoutBuf = ''
147
+ child.stdout.on('data', (chunk: Buffer) => {
148
+ stdoutBuf += chunk.toString()
149
+ const lines = stdoutBuf.split('\n')
150
+ stdoutBuf = lines.pop() ?? '' // keep last (possibly incomplete) line
151
+ for (const line of lines) {
152
+ if (line.startsWith(RESULT_PREFIX)) {
153
+ resultLine = line.slice(RESULT_PREFIX.length)
154
+ } else if (line) {
155
+ process.stdout.write(line + '\n')
156
+ }
157
+ }
158
+ })
159
+ child.stderr.on('data', (chunk: Buffer) => {
160
+ stderr += chunk.toString()
161
+ process.stderr.write(chunk)
162
+ })
163
+
164
+ child.on('close', () => {
165
+ const currentDurationMs = Date.now() - startMs
166
+
167
+ // Flush any remaining buffered stdout line (e.g. result with no trailing newline)
168
+ if (stdoutBuf.startsWith(RESULT_PREFIX)) {
169
+ resultLine = stdoutBuf.slice(RESULT_PREFIX.length)
170
+ } else if (stdoutBuf) {
171
+ process.stdout.write(stdoutBuf + '\n')
172
+ }
173
+
174
+ if (resultLine) {
175
+ try {
176
+ resolve({ ...JSON.parse(resultLine), currentDurationMs })
177
+ return
178
+ } catch { /* fall through */ }
179
+ }
180
+ resolve({ ok: false, error: stderr.trim() || 'Tool subprocess produced no output.', currentDurationMs })
181
+ })
182
+
183
+ child.on('error', (err) => {
184
+ const hint = denoProject && (err as NodeJS.ErrnoException).code === 'ENOENT'
185
+ ? ' (Deno project detected — ensure "deno" is installed and available in PATH)'
186
+ : ''
187
+ debugLog(`[elasticdash portal] Failed to spawn tool subprocess: ${err.message}${hint}`)
188
+ resolve({ ok: false, error: `Failed to spawn tool subprocess: ${err.message}${hint}`, currentDurationMs: Date.now() - startMs })
189
+ })
190
+
191
+ const payload = JSON.stringify({
192
+ toolsModulePath: pathToFileURL(toolsModulePath).pathname,
193
+ toolName,
194
+ args,
195
+ frozenEvents: frozenEvents || [],
196
+ })
197
+ child.stdin.write(payload)
198
+ child.stdin.end()
199
+ })
200
+ }
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // Tool scanning (static analysis of ed_tools.ts/js)
204
+ // ---------------------------------------------------------------------------
205
+
206
+ function resolveModulePath(fromDir: string, specifier: string): string | null {
207
+ if (!specifier.startsWith('.')) return null
208
+ const exts = ['.ts', '.tsx', '.js', '.jsx', '']
209
+ for (const ext of exts) {
210
+ const candidate = path.resolve(fromDir, specifier + ext)
211
+ if (existsSync(candidate)) return candidate
212
+ }
213
+ return null
214
+ }
215
+
216
+ function lineAt(src: string, index: number): number {
217
+ return src.slice(0, index).split('\n').length
218
+ }
219
+
220
+ function extractSource(src: string, index: number): string {
221
+ const snippet = src.slice(index, index + 2000)
222
+ return snippet.length < 2000 ? snippet : snippet + '\n// (truncated)'
223
+ }
224
+
225
+ function findFunctionInSource(src: string, name: string): { isAsync: boolean; signature: string; lineNumber?: number; sourceCode?: string } {
226
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
227
+ let m = src.match(new RegExp(`export\\s+(async\\s+)?function\\s+${escaped}\\s*(\\([^)]*\\))`))
228
+ if (m) return { isAsync: !!m[1], signature: m[2], lineNumber: lineAt(src, m.index!), sourceCode: extractSource(src, m.index!) }
229
+ m = src.match(new RegExp(`(?:^|\\n)\\s*(?:async\\s+)?function\\s+${escaped}\\s*(\\([^)]*\\))`, 'm'))
230
+ if (m) return {
231
+ isAsync: new RegExp(`async\\s+function\\s+${escaped}`).test(src),
232
+ signature: m[1],
233
+ lineNumber: lineAt(src, m.index!),
234
+ sourceCode: extractSource(src, m.index!),
235
+ }
236
+ m = src.match(new RegExp(`export\\s+const\\s+${escaped}\\s*=\\s*(async\\s*)?(\\([^)]*\\))\\s*=>`))
237
+ if (m) return { isAsync: !!m[1], signature: m[2], lineNumber: lineAt(src, m.index!) }
238
+ m = src.match(new RegExp(`(?:^|\\n)\\s*const\\s+${escaped}\\s*=\\s*(async\\s*)?(\\([^)]*\\))\\s*=>`, 'm'))
239
+ if (m) return { isAsync: !!m[1], signature: m[2], lineNumber: lineAt(src, m.index!) }
240
+ // Wrapped: const name = wrapTool('name', [async] (params) => …)
241
+ m = src.match(new RegExp(`(?:export\\s+)?const\\s+${escaped}\\s*=\\s*\\w+\\(\\s*['"][^'"]*['"]\\s*,\\s*(async\\s*)?(\\([^)]*\\))\\s*=>`))
242
+ if (m) return { isAsync: !!m[1], signature: m[2], lineNumber: lineAt(src, m.index!) }
243
+ return { isAsync: false, signature: '()' }
244
+ }
245
+
246
+ function extractExportsFromSource(filePath: string): ParsedExport[] {
247
+ let src: string
248
+ try {
249
+ src = readFileSync(filePath, 'utf8')
250
+ } catch {
251
+ return []
252
+ }
253
+ const dir = path.dirname(filePath)
254
+ const results: ParsedExport[] = []
255
+
256
+ // 1. Direct: export [async] function name(params) { … }
257
+ for (const m of src.matchAll(/export\s+(async\s+)?function\s+(\w+)\s*(\([^)]*\))/g)) {
258
+ results.push({
259
+ name: m[2], isAsync: !!m[1], signature: m[3], filePath,
260
+ lineNumber: lineAt(src, m.index!), sourceCode: extractSource(src, m.index!),
261
+ })
262
+ }
263
+
264
+ // 2. Direct: export const name = [async] (params) => …
265
+ for (const m of src.matchAll(/export\s+const\s+(\w+)\s*=\s*(async\s*)?\(([^)]*)\)\s*=>/g)) {
266
+ results.push({
267
+ name: m[1], isAsync: !!m[2], signature: `(${m[3]})`, filePath,
268
+ lineNumber: lineAt(src, m.index!), sourceCode: extractSource(src, m.index!),
269
+ })
270
+ }
271
+
272
+ // 3. Wrapped: export const name = wrapTool('name', [async] (params) => …)
273
+ for (const m of src.matchAll(/export\s+const\s+(\w+)\s*=\s*\w+\(\s*['"][^'"]*['"]\s*,\s*(async\s*)?\(([^)]*)\)\s*=>/g)) {
274
+ results.push({
275
+ name: m[1], isAsync: !!m[2], signature: `(${m[3]})`, filePath,
276
+ lineNumber: lineAt(src, m.index!), sourceCode: extractSource(src, m.index!),
277
+ })
278
+ }
279
+
280
+ // 4. Named re-exports: export { X [as Y], … } from './module'
281
+ for (const m of src.matchAll(/export\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g)) {
282
+ const modulePath = resolveModulePath(dir, m[2])
283
+ let moduleSrc = ''
284
+ try { if (modulePath) moduleSrc = readFileSync(modulePath, 'utf8') } catch { /* ignore */ }
285
+ for (const spec of m[1].split(',')) {
286
+ const parts = spec.trim().split(/\s+as\s+/)
287
+ const originalName = parts[0].trim()
288
+ const exportedName = (parts[1] ?? parts[0]).trim()
289
+ if (!exportedName || exportedName === 'default') continue
290
+ const info = moduleSrc ? findFunctionInSource(moduleSrc, originalName) : { isAsync: false, signature: '()' }
291
+ results.push({
292
+ name: exportedName, isAsync: info.isAsync, signature: info.signature,
293
+ filePath: modulePath ?? filePath, lineNumber: info.lineNumber, sourceCode: info.sourceCode,
294
+ })
295
+ }
296
+ }
297
+
298
+ // 5. Import + destructure: import { obj } from './m' + export const { a, b } = obj
299
+ for (const imp of src.matchAll(/import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g)) {
300
+ const importedNames = imp[1].split(',').map(s => {
301
+ const parts = s.trim().split(/\s+as\s+/)
302
+ return { original: parts[0].trim(), local: (parts[1] ?? parts[0]).trim() }
303
+ }).filter(n => n.local)
304
+ const modulePath = resolveModulePath(dir, imp[2])
305
+ for (const { local } of importedNames) {
306
+ const destructureRe = new RegExp(`export\\s+const\\s+\\{([^}]+)\\}\\s*=\\s*${local.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)
307
+ const dm = src.match(destructureRe)
308
+ if (!dm) continue
309
+ let moduleSrc = ''
310
+ try { if (modulePath) moduleSrc = readFileSync(modulePath, 'utf8') } catch { /* ignore */ }
311
+ for (const member of dm[1].split(',')) {
312
+ const name = member.trim()
313
+ if (!name) continue
314
+ const info = moduleSrc ? findFunctionInSource(moduleSrc, name) : { isAsync: false, signature: '()' }
315
+ results.push({
316
+ name, isAsync: info.isAsync, signature: info.signature,
317
+ filePath: modulePath ?? filePath, lineNumber: info.lineNumber, sourceCode: info.sourceCode,
318
+ })
319
+ }
320
+ }
321
+ }
322
+
323
+ return results
324
+ }
325
+
326
+ export function scanTools(cwd: string): ToolInfo[] {
327
+ for (const candidate of [path.join(cwd, 'ed_tools.ts'), path.join(cwd, 'ed_tools.js')]) {
328
+ if (!existsSync(candidate)) continue
329
+ const exports = extractExportsFromSource(candidate)
330
+ if (exports.length > 0) {
331
+ return exports.map(e => ({
332
+ name: e.name, isAsync: e.isAsync, signature: e.signature,
333
+ filePath: e.filePath, lineNumber: e.lineNumber, sourceCode: e.sourceCode,
334
+ }))
335
+ }
336
+ }
337
+ return []
338
+ }
339
+
340
+ export function scanWorkflows(cwd: string): ToolInfo[] {
341
+ for (const candidate of [path.join(cwd, 'ed_workflows.ts'), path.join(cwd, 'ed_workflows.js')]) {
342
+ if (!existsSync(candidate)) continue
343
+ const exports = extractExportsFromSource(candidate)
344
+ if (exports.length > 0) {
345
+ return exports.map(e => ({
346
+ name: e.name, isAsync: e.isAsync, signature: e.signature,
347
+ filePath: e.filePath, lineNumber: e.lineNumber, sourceCode: e.sourceCode,
348
+ }))
349
+ }
350
+ }
351
+ return []
352
+ }