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,386 @@
1
+ // Mark this process as an Elasticdash worker before anything else runs
2
+ ;(globalThis as any).__ELASTICDASH_WORKER__ = true
3
+
4
+ // Ensure .env is loaded in the worker subprocess
5
+ import 'dotenv/config'
6
+ /**
7
+ * workflow-runner-worker.ts
8
+ *
9
+ * Subprocess entry point for running a workflow function in an isolated Node.js
10
+ * process with a fresh ESM module cache, guaranteeing that packages with only an
11
+ * "import" exports condition (e.g. jaison) resolve correctly.
12
+ *
13
+ * Protocol (via stdin/stdout):
14
+ * stdin — one JSON line:
15
+ * { workflowsModulePath, toolsModulePath?, workflowName, args, input }
16
+ * stdout — prefixed result line: __ELASTICDASH_RESULT__:{...json...}
17
+ */
18
+
19
+ import { startTraceSession, setCurrentTrace } from './trace-adapter/context.js'
20
+ import { installAIInterceptor, uninstallAIInterceptor } from './interceptors/ai-interceptor.js'
21
+ import { TraceRecorder, setCaptureContext, getCaptureContext } from './capture/recorder.js'
22
+ import { ReplayController } from './capture/replay.js'
23
+ import { interceptFetch, restoreFetch } from './interceptors/http.js'
24
+ import { interceptRandom, restoreRandom, interceptDateNow, restoreDateNow, rawDateNow } from './interceptors/side-effects.js'
25
+ import { installDBAutoInterceptor, uninstallDBAutoInterceptor } from './interceptors/db-auto.js'
26
+ import { pathToFileURL } from 'node:url'
27
+ import type { TraceHandle } from './trace-adapter/context.js'
28
+ import type { WorkflowEvent } from './capture/event.js'
29
+ import type { AgentState } from './types/agent.js'
30
+ import fs from 'node:fs'
31
+
32
+ const TOOL_WRAPPER_ACTIVE_KEY = '__elasticdash_tool_wrapper_active__'
33
+
34
+ async function readStdin(): Promise<string> {
35
+ let raw = ''
36
+ for await (const chunk of process.stdin) {
37
+ raw += chunk
38
+ }
39
+ return raw.trim()
40
+ }
41
+
42
+
43
+ const RESULT_PREFIX = '__ELASTICDASH_RESULT__:'
44
+
45
+ const IS_DENO = typeof (globalThis as any).Deno !== 'undefined'
46
+
47
+ /** Write the result JSON to fd3 pipe (Node.js) or a prefixed stdout line (Deno).
48
+ * Under Deno, fs.write on fd3 silently drops data before process exit, so we
49
+ * use stdout with a unique prefix that the parent dashboard server reads. */
50
+ function writeResult(result: unknown): Promise<void> {
51
+ return new Promise((resolve) => {
52
+ const json = JSON.stringify(result)
53
+ if (IS_DENO) {
54
+ process.stdout.write(RESULT_PREFIX + json + '\n', () => resolve())
55
+ return
56
+ }
57
+ try {
58
+ fs.write(3, json + '\n', (err) => {
59
+ if (err) {
60
+ process.stdout.write(RESULT_PREFIX + json + '\n', () => resolve())
61
+ } else {
62
+ resolve()
63
+ }
64
+ })
65
+ } catch {
66
+ process.stdout.write(RESULT_PREFIX + json + '\n', () => resolve())
67
+ }
68
+ })
69
+ }
70
+
71
+ /** Per-tool mock configuration sent from the dashboard UI */
72
+ interface ToolMockEntry {
73
+ mode: 'live' | 'mock-all' | 'mock-specific'
74
+ callIndices?: number[]
75
+ mockData?: Record<number, unknown>
76
+ }
77
+
78
+ interface ToolMockConfig {
79
+ [toolName: string]: ToolMockEntry
80
+ }
81
+
82
+ /** Per-model AI mock configuration (mirrors ToolMockConfig) */
83
+ interface AIMockEntry {
84
+ mode: 'live' | 'mock-all' | 'mock-specific'
85
+ callIndices?: number[]
86
+ mockData?: Record<number, unknown>
87
+ }
88
+
89
+ interface AIMockConfig {
90
+ [modelName: string]: AIMockEntry
91
+ }
92
+
93
+ /** Minimal inline tool-wrapping — records each tool call to the trace. */
94
+ async function loadAndWrapTools(
95
+ toolsModulePath: string,
96
+ trace: TraceHandle,
97
+ toolMockConfig?: ToolMockConfig,
98
+ ): Promise<Record<string, (...a: unknown[]) => unknown>> {
99
+ try {
100
+ // Use absolute file URL for ESM import
101
+ const toolsMod = await import(pathToFileURL(toolsModulePath).href)
102
+ const wrapped: Record<string, (...a: unknown[]) => unknown> = {}
103
+ // Track per-tool call counters for mock-specific mode
104
+ const toolCallCounters: Record<string, number> = {}
105
+ for (const [name, fn] of Object.entries(toolsMod)) {
106
+ if (typeof fn !== 'function') continue
107
+ const mockEntry = toolMockConfig?.[name]
108
+ wrapped[name] = new Proxy(fn as (...a: unknown[]) => unknown, {
109
+ apply(target, thisArg, args) {
110
+ const recordedArgs = args.length === 1 ? args[0] : args
111
+ const ctx = getCaptureContext()
112
+ const id = ctx ? ctx.recorder.nextId() : -1
113
+ const start = rawDateNow()
114
+
115
+ // Replay: return historical result without executing
116
+ if (ctx && ctx.replay.shouldReplay(id)) {
117
+ const historical = ctx.replay.getRecordedEvent(id)
118
+ if (historical) ctx.recorder.record(historical)
119
+ const replayed = ctx.replay.getRecordedResult(id)
120
+ trace.recordToolCall({ name, args: recordedArgs, result: replayed, workflowEventId: id })
121
+ return replayed
122
+ }
123
+
124
+ // Mock: check if this tool call should be mocked
125
+ if (mockEntry && mockEntry.mode !== 'live') {
126
+ // Increment call counter for this tool (1-based)
127
+ if (!toolCallCounters[name]) toolCallCounters[name] = 0
128
+ toolCallCounters[name]++
129
+ const callNumber = toolCallCounters[name]
130
+
131
+ let shouldMock = false
132
+ let mockResult: unknown
133
+
134
+ if (mockEntry.mode === 'mock-all') {
135
+ shouldMock = true
136
+ // Use per-call mock data if available, otherwise default (key 0)
137
+ const data = mockEntry.mockData ?? {}
138
+ mockResult = data[callNumber] !== undefined ? data[callNumber] : data[0]
139
+ } else if (mockEntry.mode === 'mock-specific') {
140
+ const indices = mockEntry.callIndices ?? []
141
+ if (indices.includes(callNumber)) {
142
+ shouldMock = true
143
+ const data = mockEntry.mockData ?? {}
144
+ mockResult = data[callNumber]
145
+ }
146
+ }
147
+
148
+ if (shouldMock) {
149
+ if (ctx) ctx.recorder.record({ id, type: 'tool', name, input: recordedArgs, output: mockResult, timestamp: start, durationMs: 0 })
150
+ trace.recordToolCall({ name, args: recordedArgs, result: mockResult, workflowEventId: id })
151
+ return Promise.resolve(mockResult)
152
+ }
153
+ } else {
154
+ // Still track call count even for live tools
155
+ if (!toolCallCounters[name]) toolCallCounters[name] = 0
156
+ toolCallCounters[name]++
157
+ }
158
+
159
+ const g = globalThis as Record<string, unknown>
160
+ const prev = g[TOOL_WRAPPER_ACTIVE_KEY]
161
+ const restoreWrapperFlag = () => {
162
+ if (prev === undefined) delete g[TOOL_WRAPPER_ACTIVE_KEY]
163
+ else g[TOOL_WRAPPER_ACTIVE_KEY] = prev
164
+ }
165
+
166
+ g[TOOL_WRAPPER_ACTIVE_KEY] = true
167
+
168
+ let result: unknown
169
+ try {
170
+ result = Reflect.apply(target, thisArg, args)
171
+ } catch (e) {
172
+ restoreWrapperFlag()
173
+ throw e
174
+ }
175
+
176
+ if (result && typeof (result as Promise<unknown>).then === 'function') {
177
+ return (result as Promise<unknown>).then((v: unknown) => {
178
+ restoreWrapperFlag()
179
+ const durationMs = rawDateNow() - start
180
+ if (ctx) ctx.recorder.record({ id, type: 'tool', name, input: recordedArgs, output: v, timestamp: start, durationMs })
181
+ trace.recordToolCall({ name, args: recordedArgs, result: v, workflowEventId: id, durationMs })
182
+ return v
183
+ }).catch((e: unknown) => {
184
+ restoreWrapperFlag()
185
+ const durationMs = rawDateNow() - start
186
+ if (ctx) ctx.recorder.record({ id, type: 'tool', name, input: recordedArgs, output: { error: String(e) }, timestamp: start, durationMs })
187
+ trace.recordToolCall({ name, args: recordedArgs, result: { error: String(e) }, workflowEventId: id, durationMs })
188
+ throw e
189
+ })
190
+ }
191
+ restoreWrapperFlag()
192
+ const durationMs = rawDateNow() - start
193
+ if (ctx) ctx.recorder.record({ id, type: 'tool', name, input: recordedArgs, output: result, timestamp: start, durationMs })
194
+ trace.recordToolCall({ name, args: recordedArgs, result, workflowEventId: id, durationMs })
195
+ return result
196
+ },
197
+ })
198
+ }
199
+ return wrapped
200
+ } catch {
201
+ return {}
202
+ }
203
+ }
204
+
205
+ async function main() {
206
+ // Keep a reference to the real process.exit so we can call it after flushing stdout.
207
+ const originalExit = process.exit.bind(process)
208
+
209
+ const raw = await readStdin()
210
+
211
+ let payload: {
212
+ workflowsModulePath: string
213
+ toolsModulePath?: string
214
+ workflowName: string
215
+ args: unknown[]
216
+ input: unknown
217
+ replayMode?: boolean
218
+ checkpoint?: number
219
+ history?: WorkflowEvent[]
220
+ /** Optional agent state for mid-trace agent resumption */
221
+ agentState?: AgentState
222
+ /** Optional tool mock configuration from the dashboard UI */
223
+ toolMockConfig?: ToolMockConfig
224
+ /** Optional AI/LLM mock configuration from the dashboard UI */
225
+ aiMockConfig?: AIMockConfig
226
+ /** Optional prompt mock config: system prompt overrides keyed by original system prompt text */
227
+ promptMockConfig?: Record<string, unknown>
228
+ /** Optional user prompt mock config: keyed by original user message text */
229
+ userPromptMockConfig?: Record<string, { mode: 'live' | 'replace-all' | 'replace-specific'; replacement: string; callIndices?: number[] }>
230
+ }
231
+ try {
232
+ payload = JSON.parse(raw)
233
+ } catch (e) {
234
+ await writeResult({ ok: false, error: `Invalid JSON input: ${(e as Error).message}` })
235
+ originalExit(1)
236
+ return
237
+ }
238
+
239
+ const { workflowsModulePath, toolsModulePath, workflowName, args, input, replayMode = false, checkpoint = 0, history = [], agentState, toolMockConfig, aiMockConfig, promptMockConfig, userPromptMockConfig } = payload
240
+
241
+ const { context, finalise } = startTraceSession()
242
+ setCurrentTrace(context.trace)
243
+
244
+ const recorder = new TraceRecorder()
245
+ const replay = new ReplayController(replayMode, checkpoint, history)
246
+ setCaptureContext({ recorder, replay })
247
+
248
+ // Inject wrapped tools into global scope so the workflow can call them
249
+ // NOTE: This only works if the workflow accesses tools as globals, not via import.
250
+ // If the workflow uses import { tool } from './tools', the injected global will NOT be used.
251
+ // For maximum robustness, prefer passing tools as explicit arguments or context.
252
+ const globals = global as Record<string, unknown>
253
+ const originalValues: Record<string, unknown> = {}
254
+ let wrappedTools: Record<string, (...a: unknown[]) => unknown> = {}
255
+
256
+ if (toolsModulePath) {
257
+ wrappedTools = await loadAndWrapTools(toolsModulePath, context.trace, toolMockConfig)
258
+ for (const [name, fn] of Object.entries(wrappedTools)) {
259
+ originalValues[name] = globals[name]
260
+ globals[name] = fn
261
+ }
262
+ }
263
+
264
+ // Intercept process.exit() so that workflows that call it internally (e.g. agent
265
+ // frameworks that call process.exit(0) after completing) don't kill the subprocess
266
+ // before we write the result.
267
+ // WARNING: This only intercepts process.exit() in this scope. Libraries that cache their own reference to process.exit may still terminate the process.
268
+ let pendingExitCode: number | undefined
269
+ ;(process as NodeJS.Process).exit = (code?: number) => {
270
+ pendingExitCode = code ?? 0
271
+ return undefined as never
272
+ }
273
+
274
+ let currentOutput: unknown
275
+ let workflowError: Error | undefined
276
+
277
+ try {
278
+ // Write mock configs to globals so module-imported tools/AI can read them via resolveMock/resolveAIMock()
279
+ ;(globalThis as any).__ELASTICDASH_TOOL_MOCKS__ = toolMockConfig ?? {}
280
+ ;(globalThis as any).__ELASTICDASH_TOOL_CALL_COUNTERS__ = {}
281
+ ;(globalThis as any).__ELASTICDASH_AI_MOCKS__ = aiMockConfig ?? {}
282
+ ;(globalThis as any).__ELASTICDASH_AI_CALL_COUNTERS__ = {}
283
+ ;(globalThis as any).__ELASTICDASH_PROMPT_MOCKS__ = promptMockConfig ?? {}
284
+ ;(globalThis as any).__ELASTICDASH_PROMPT_CALL_COUNTERS__ = {}
285
+ ;(globalThis as any).__ELASTICDASH_USER_PROMPT_MOCKS__ = userPromptMockConfig ?? {}
286
+ ;(globalThis as any).__ELASTICDASH_USER_PROMPT_CALL_COUNTERS__ = {}
287
+
288
+ await installDBAutoInterceptor()
289
+ installAIInterceptor()
290
+ interceptFetch()
291
+ interceptRandom()
292
+ interceptDateNow()
293
+
294
+ try {
295
+ if (agentState) {
296
+ // Agent mid-trace resumption path: load ed_agents and resume from saved state
297
+ const agentsModulePath = workflowsModulePath.replace(/ed_workflows(\.[^.]+)?$/, 'ed_agents$1')
298
+ const agentsMod = await import(pathToFileURL(agentsModulePath).href)
299
+ if (typeof agentsMod.resumeAgentFromTrace !== 'function') {
300
+ throw new Error(`"resumeAgentFromTrace" is not an exported function in ${agentsModulePath}`)
301
+ }
302
+ currentOutput = await (agentsMod.resumeAgentFromTrace as (s: AgentState) => Promise<unknown>)(agentState)
303
+ console.error('[worker] resumeAgentFromTrace resolved, currentOutput:', currentOutput)
304
+ } else {
305
+ // Standard workflow path
306
+ const workflowsMod = await import(pathToFileURL(workflowsModulePath).href)
307
+ const workflowFn = workflowsMod[workflowName]
308
+ if (typeof workflowFn !== 'function') {
309
+ ;(process as NodeJS.Process).exit = originalExit
310
+ await writeResult({ ok: false, error: `"${workflowName}" is not an exported function in the workflow module.` })
311
+ originalExit(1)
312
+ return
313
+ }
314
+ // Standardize workflow argument resolution: always pass [input] if args is empty
315
+ const callArgs = args.length ? args : [input]
316
+ currentOutput = await (workflowFn as (...a: unknown[]) => unknown)(...callArgs)
317
+ console.error('[worker] workflowFn resolved, currentOutput:', currentOutput) // stderr so it's visible
318
+ }
319
+ } finally {
320
+ uninstallAIInterceptor()
321
+ restoreFetch()
322
+ restoreRandom()
323
+ restoreDateNow()
324
+ uninstallDBAutoInterceptor()
325
+ }
326
+ } catch (e) {
327
+ workflowError = e instanceof Error ? e : new Error(String(e))
328
+ } finally {
329
+ // Restore real process.exit before any further async work
330
+ ;(process as NodeJS.Process).exit = originalExit
331
+
332
+ // Restore injected globals
333
+ for (const [name, original] of Object.entries(originalValues)) {
334
+ if (original === undefined) {
335
+ delete globals[name]
336
+ } else {
337
+ globals[name] = original
338
+ }
339
+ }
340
+ setCurrentTrace(undefined)
341
+ setCaptureContext(undefined)
342
+ finalise()
343
+
344
+ // Clear mock globals
345
+ delete (globalThis as any).__ELASTICDASH_TOOL_MOCKS__
346
+ delete (globalThis as any).__ELASTICDASH_TOOL_CALL_COUNTERS__
347
+ delete (globalThis as any).__ELASTICDASH_AI_MOCKS__
348
+ delete (globalThis as any).__ELASTICDASH_AI_CALL_COUNTERS__
349
+ delete (globalThis as any).__ELASTICDASH_PROMPT_MOCKS__
350
+ delete (globalThis as any).__ELASTICDASH_PROMPT_CALL_COUNTERS__
351
+ delete (globalThis as any).__ELASTICDASH_USER_PROMPT_MOCKS__
352
+ delete (globalThis as any).__ELASTICDASH_USER_PROMPT_CALL_COUNTERS__
353
+ }
354
+
355
+ await recorder.flush()
356
+
357
+ const traceData = {
358
+ steps: context.trace.getSteps(),
359
+ llmSteps: context.trace.getLLMSteps(),
360
+ toolCalls: context.trace.getToolCalls(),
361
+ customSteps: context.trace.getCustomSteps(),
362
+ workflowTrace: recorder.toTrace(),
363
+ }
364
+
365
+ if (workflowError) {
366
+ await writeResult({ ok: false, error: workflowError.message ?? String(workflowError), ...traceData })
367
+ originalExit(pendingExitCode ?? 1)
368
+ } else {
369
+ await writeResult({ ok: true, currentOutput, ...traceData })
370
+ originalExit(pendingExitCode ?? 0)
371
+ }
372
+ }
373
+
374
+
375
+ main().catch((err) => {
376
+ const errorJson = JSON.stringify({ ok: false, error: err?.message ?? String(err) })
377
+ if (IS_DENO) {
378
+ process.stdout.write(RESULT_PREFIX + errorJson + '\n', () => { process.exit(1) })
379
+ return
380
+ }
381
+ try {
382
+ fs.write(3, errorJson + '\n', () => { process.exit(1) })
383
+ } catch {
384
+ process.stdout.write(RESULT_PREFIX + errorJson + '\n', () => { process.exit(1) })
385
+ }
386
+ })
@@ -0,0 +1,58 @@
1
+ import { TraceRecorder, setCaptureContext } from './capture/recorder.js'
2
+ import { ReplayController } from './capture/replay.js'
3
+ import { interceptFetch, restoreFetch } from './interceptors/http.js'
4
+ import { interceptRandom, restoreRandom, interceptDateNow, restoreDateNow } from './interceptors/side-effects.js'
5
+ import { maybeCaptureTrace } from './ci/trace-writer.js'
6
+ import type { WorkflowEvent, WorkflowTrace } from './capture/event.js'
7
+
8
+ export interface RunWorkflowOptions {
9
+ replayMode?: boolean
10
+ checkpoint?: number
11
+ history?: WorkflowEvent[]
12
+ interceptHttp?: boolean
13
+ interceptSideEffects?: boolean
14
+ }
15
+
16
+ export interface WorkflowRunResult<T = unknown> {
17
+ result: T
18
+ trace: WorkflowTrace
19
+ }
20
+
21
+ export async function runWorkflow<T = unknown>(
22
+ workflowFn: () => Promise<T>,
23
+ options: RunWorkflowOptions = {},
24
+ ): Promise<WorkflowRunResult<T>> {
25
+ const {
26
+ replayMode = false,
27
+ checkpoint = 0,
28
+ history = [],
29
+ interceptHttp = true,
30
+ interceptSideEffects = true,
31
+ } = options
32
+
33
+ const recorder = new TraceRecorder()
34
+ const replay = new ReplayController(replayMode, checkpoint, history)
35
+
36
+ setCaptureContext({ recorder, replay })
37
+
38
+ if (interceptHttp) interceptFetch()
39
+ if (interceptSideEffects) {
40
+ interceptRandom()
41
+ interceptDateNow()
42
+ }
43
+
44
+ try {
45
+ const result = await workflowFn()
46
+ await recorder.flush()
47
+ const trace = recorder.toTrace()
48
+ await maybeCaptureTrace(trace.events, trace.traceId)
49
+ return { result, trace }
50
+ } finally {
51
+ if (interceptHttp) restoreFetch()
52
+ if (interceptSideEffects) {
53
+ restoreRandom()
54
+ restoreDateNow()
55
+ }
56
+ setCaptureContext(undefined)
57
+ }
58
+ }