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.
- package/LICENSE +21 -0
- package/README.md +775 -0
- package/dist/browser-ui.d.ts +43 -0
- package/dist/browser-ui.d.ts.map +1 -0
- package/dist/browser-ui.js +246 -0
- package/dist/browser-ui.js.map +1 -0
- package/dist/capture/event.d.ts +33 -0
- package/dist/capture/event.d.ts.map +1 -0
- package/dist/capture/event.js +2 -0
- package/dist/capture/event.js.map +1 -0
- package/dist/capture/index.d.ts +4 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +4 -0
- package/dist/capture/index.js.map +1 -0
- package/dist/capture/recorder.d.ts +24 -0
- package/dist/capture/recorder.d.ts.map +1 -0
- package/dist/capture/recorder.js +46 -0
- package/dist/capture/recorder.js.map +1 -0
- package/dist/capture/replay.d.ts +20 -0
- package/dist/capture/replay.d.ts.map +1 -0
- package/dist/capture/replay.js +47 -0
- package/dist/capture/replay.js.map +1 -0
- package/dist/ci/api-client.d.ts +38 -0
- package/dist/ci/api-client.d.ts.map +1 -0
- package/dist/ci/api-client.js +96 -0
- package/dist/ci/api-client.js.map +1 -0
- package/dist/ci/benchmark.d.ts +33 -0
- package/dist/ci/benchmark.d.ts.map +1 -0
- package/dist/ci/benchmark.js +213 -0
- package/dist/ci/benchmark.js.map +1 -0
- package/dist/ci/ed-runner.d.ts +48 -0
- package/dist/ci/ed-runner.d.ts.map +1 -0
- package/dist/ci/ed-runner.js +260 -0
- package/dist/ci/ed-runner.js.map +1 -0
- package/dist/ci/executor.d.ts +13 -0
- package/dist/ci/executor.d.ts.map +1 -0
- package/dist/ci/executor.js +542 -0
- package/dist/ci/executor.js.map +1 -0
- package/dist/ci/git-info.d.ts +17 -0
- package/dist/ci/git-info.d.ts.map +1 -0
- package/dist/ci/git-info.js +102 -0
- package/dist/ci/git-info.js.map +1 -0
- package/dist/ci/index.d.ts +6 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +4 -0
- package/dist/ci/index.js.map +1 -0
- package/dist/ci/measurement.d.ts +9 -0
- package/dist/ci/measurement.d.ts.map +1 -0
- package/dist/ci/measurement.js +15 -0
- package/dist/ci/measurement.js.map +1 -0
- package/dist/ci/replay.d.ts +31 -0
- package/dist/ci/replay.d.ts.map +1 -0
- package/dist/ci/replay.js +96 -0
- package/dist/ci/replay.js.map +1 -0
- package/dist/ci/reporters/default.d.ts +8 -0
- package/dist/ci/reporters/default.d.ts.map +1 -0
- package/dist/ci/reporters/default.js +46 -0
- package/dist/ci/reporters/default.js.map +1 -0
- package/dist/ci/reporters/index.d.ts +8 -0
- package/dist/ci/reporters/index.d.ts.map +1 -0
- package/dist/ci/reporters/index.js +14 -0
- package/dist/ci/reporters/index.js.map +1 -0
- package/dist/ci/reporters/json.d.ts +8 -0
- package/dist/ci/reporters/json.d.ts.map +1 -0
- package/dist/ci/reporters/json.js +14 -0
- package/dist/ci/reporters/json.js.map +1 -0
- package/dist/ci/reporters/junit.d.ts +8 -0
- package/dist/ci/reporters/junit.d.ts.map +1 -0
- package/dist/ci/reporters/junit.js +48 -0
- package/dist/ci/reporters/junit.js.map +1 -0
- package/dist/ci/runner.d.ts +3 -0
- package/dist/ci/runner.d.ts.map +1 -0
- package/dist/ci/runner.js +187 -0
- package/dist/ci/runner.js.map +1 -0
- package/dist/ci/test-discovery.d.ts +5 -0
- package/dist/ci/test-discovery.d.ts.map +1 -0
- package/dist/ci/test-discovery.js +11 -0
- package/dist/ci/test-discovery.js.map +1 -0
- package/dist/ci/test-loader.d.ts +19 -0
- package/dist/ci/test-loader.d.ts.map +1 -0
- package/dist/ci/test-loader.js +149 -0
- package/dist/ci/test-loader.js.map +1 -0
- package/dist/ci/test-registry.d.ts +42 -0
- package/dist/ci/test-registry.d.ts.map +1 -0
- package/dist/ci/test-registry.js +18 -0
- package/dist/ci/test-registry.js.map +1 -0
- package/dist/ci/trace-schema.d.ts +30 -0
- package/dist/ci/trace-schema.d.ts.map +1 -0
- package/dist/ci/trace-schema.js +66 -0
- package/dist/ci/trace-schema.js.map +1 -0
- package/dist/ci/trace-writer.d.ts +16 -0
- package/dist/ci/trace-writer.d.ts.map +1 -0
- package/dist/ci/trace-writer.js +108 -0
- package/dist/ci/trace-writer.js.map +1 -0
- package/dist/ci/types.d.ts +108 -0
- package/dist/ci/types.d.ts.map +1 -0
- package/dist/ci/types.js +3 -0
- package/dist/ci/types.js.map +1 -0
- package/dist/ci/upload-client.d.ts +74 -0
- package/dist/ci/upload-client.d.ts.map +1 -0
- package/dist/ci/upload-client.js +195 -0
- package/dist/ci/upload-client.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +716 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/agent-state.d.ts +47 -0
- package/dist/core/agent-state.d.ts.map +1 -0
- package/dist/core/agent-state.js +137 -0
- package/dist/core/agent-state.js.map +1 -0
- package/dist/core/judge-utils.d.ts +22 -0
- package/dist/core/judge-utils.d.ts.map +1 -0
- package/dist/core/judge-utils.js +211 -0
- package/dist/core/judge-utils.js.map +1 -0
- package/dist/core/registry.d.ts +28 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +52 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/dashboard-server.d.ts +65 -0
- package/dist/dashboard-server.d.ts.map +1 -0
- package/dist/dashboard-server.js +3940 -0
- package/dist/dashboard-server.js.map +1 -0
- package/dist/execution/tool-runner.d.ts +26 -0
- package/dist/execution/tool-runner.d.ts.map +1 -0
- package/dist/execution/tool-runner.js +316 -0
- package/dist/execution/tool-runner.js.map +1 -0
- package/dist/html/dashboard.html +2218 -0
- package/dist/http.d.ts +14 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +13 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +8102 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/ai-interceptor.d.ts +26 -0
- package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
- package/dist/interceptors/ai-interceptor.js +756 -0
- package/dist/interceptors/ai-interceptor.js.map +1 -0
- package/dist/interceptors/db-auto.d.ts +8 -0
- package/dist/interceptors/db-auto.d.ts.map +1 -0
- package/dist/interceptors/db-auto.js +217 -0
- package/dist/interceptors/db-auto.js.map +1 -0
- package/dist/interceptors/db.d.ts +23 -0
- package/dist/interceptors/db.d.ts.map +1 -0
- package/dist/interceptors/db.js +137 -0
- package/dist/interceptors/db.js.map +1 -0
- package/dist/interceptors/http.d.ts +28 -0
- package/dist/interceptors/http.d.ts.map +1 -0
- package/dist/interceptors/http.js +356 -0
- package/dist/interceptors/http.js.map +1 -0
- package/dist/interceptors/side-effects.d.ts +7 -0
- package/dist/interceptors/side-effects.d.ts.map +1 -0
- package/dist/interceptors/side-effects.js +72 -0
- package/dist/interceptors/side-effects.js.map +1 -0
- package/dist/interceptors/telemetry-push.d.ts +142 -0
- package/dist/interceptors/telemetry-push.d.ts.map +1 -0
- package/dist/interceptors/telemetry-push.js +463 -0
- package/dist/interceptors/telemetry-push.js.map +1 -0
- package/dist/interceptors/tool.d.ts +2 -0
- package/dist/interceptors/tool.d.ts.map +1 -0
- package/dist/interceptors/tool.js +274 -0
- package/dist/interceptors/tool.js.map +1 -0
- package/dist/interceptors/workflow-ai.d.ts +5 -0
- package/dist/interceptors/workflow-ai.d.ts.map +1 -0
- package/dist/interceptors/workflow-ai.js +382 -0
- package/dist/interceptors/workflow-ai.js.map +1 -0
- package/dist/internals/conditional-recorder.d.ts +21 -0
- package/dist/internals/conditional-recorder.d.ts.map +1 -0
- package/dist/internals/conditional-recorder.js +54 -0
- package/dist/internals/conditional-recorder.js.map +1 -0
- package/dist/internals/mock-resolver.d.ts +146 -0
- package/dist/internals/mock-resolver.d.ts.map +1 -0
- package/dist/internals/mock-resolver.js +427 -0
- package/dist/internals/mock-resolver.js.map +1 -0
- package/dist/matchers/index.d.ts +96 -0
- package/dist/matchers/index.d.ts.map +1 -0
- package/dist/matchers/index.js +668 -0
- package/dist/matchers/index.js.map +1 -0
- package/dist/observability.d.ts +82 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +471 -0
- package/dist/observability.js.map +1 -0
- package/dist/portal-executor.d.ts +30 -0
- package/dist/portal-executor.d.ts.map +1 -0
- package/dist/portal-executor.js +324 -0
- package/dist/portal-executor.js.map +1 -0
- package/dist/portal-server.d.ts +3 -0
- package/dist/portal-server.d.ts.map +1 -0
- package/dist/portal-server.js +279 -0
- package/dist/portal-server.js.map +1 -0
- package/dist/proxy/llm-capture.d.ts +14 -0
- package/dist/proxy/llm-capture.d.ts.map +1 -0
- package/dist/proxy/llm-capture.js +264 -0
- package/dist/proxy/llm-capture.js.map +1 -0
- package/dist/reporter.d.ts +3 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +72 -0
- package/dist/reporter.js.map +1 -0
- package/dist/runWorkflowSubprocess.d.ts +14 -0
- package/dist/runWorkflowSubprocess.d.ts.map +1 -0
- package/dist/runWorkflowSubprocess.js +66 -0
- package/dist/runWorkflowSubprocess.js.map +1 -0
- package/dist/runner.d.ts +16 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +138 -0
- package/dist/runner.js.map +1 -0
- package/dist/socket-connector.d.ts +22 -0
- package/dist/socket-connector.d.ts.map +1 -0
- package/dist/socket-connector.js +104 -0
- package/dist/socket-connector.js.map +1 -0
- package/dist/telemetry-batcher.d.ts +56 -0
- package/dist/telemetry-batcher.d.ts.map +1 -0
- package/dist/telemetry-batcher.js +143 -0
- package/dist/telemetry-batcher.js.map +1 -0
- package/dist/test-setup.d.ts +12 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +13 -0
- package/dist/test-setup.js.map +1 -0
- package/dist/tool-registry.d.ts +31 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +73 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tool-runner-worker.d.ts +2 -0
- package/dist/tool-runner-worker.d.ts.map +1 -0
- package/dist/tool-runner-worker.js +215 -0
- package/dist/tool-runner-worker.js.map +1 -0
- package/dist/trace-adapter/context.d.ts +72 -0
- package/dist/trace-adapter/context.d.ts.map +1 -0
- package/dist/trace-adapter/context.js +80 -0
- package/dist/trace-adapter/context.js.map +1 -0
- package/dist/tracing.d.ts +2 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +59 -0
- package/dist/tracing.js.map +1 -0
- package/dist/trigger-executor.d.ts +12 -0
- package/dist/trigger-executor.d.ts.map +1 -0
- package/dist/trigger-executor.js +130 -0
- package/dist/trigger-executor.js.map +1 -0
- package/dist/types/portal.d.ts +76 -0
- package/dist/types/portal.d.ts.map +1 -0
- package/dist/types/portal.js +2 -0
- package/dist/types/portal.js.map +1 -0
- package/dist/utils/debug.d.ts +3 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +8 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/license-error.d.ts +23 -0
- package/dist/utils/license-error.d.ts.map +1 -0
- package/dist/utils/license-error.js +42 -0
- package/dist/utils/license-error.js.map +1 -0
- package/dist/utils/redact.d.ts +7 -0
- package/dist/utils/redact.d.ts.map +1 -0
- package/dist/utils/redact.js +26 -0
- package/dist/utils/redact.js.map +1 -0
- package/dist/workflow-runner-worker.d.ts +2 -0
- package/dist/workflow-runner-worker.d.ts.map +1 -0
- package/dist/workflow-runner-worker.js +329 -0
- package/dist/workflow-runner-worker.js.map +1 -0
- package/dist/workflow-runner.d.ts +14 -0
- package/dist/workflow-runner.d.ts.map +1 -0
- package/dist/workflow-runner.js +34 -0
- package/dist/workflow-runner.js.map +1 -0
- package/docs/agent-coding-instructions.md +138 -0
- package/docs/agent-integration-guide.md +564 -0
- package/docs/agents.md +140 -0
- package/docs/dashboard.md +394 -0
- package/docs/deno.md +69 -0
- package/docs/instrumentation.md +424 -0
- package/docs/langfuse-trace-structure.md +145 -0
- package/docs/matchers.md +173 -0
- package/docs/observability_contract.md +192 -0
- package/docs/observability_mode.md +195 -0
- package/docs/quickstart.md +621 -0
- package/docs/security-compliance.md +566 -0
- package/docs/test-writing-guidelines.md +444 -0
- package/docs/tools.md +165 -0
- package/docs/workflow-modes.md +253 -0
- package/package.json +76 -0
- package/src/browser-ui.ts +281 -0
- package/src/capture/event.ts +30 -0
- package/src/capture/index.ts +3 -0
- package/src/capture/recorder.ts +62 -0
- package/src/capture/replay.ts +55 -0
- package/src/ci/api-client.ts +136 -0
- package/src/ci/benchmark.ts +257 -0
- package/src/ci/ed-runner.ts +351 -0
- package/src/ci/executor.ts +671 -0
- package/src/ci/git-info.ts +127 -0
- package/src/ci/index.ts +5 -0
- package/src/ci/measurement.ts +25 -0
- package/src/ci/replay.ts +127 -0
- package/src/ci/reporters/default.ts +50 -0
- package/src/ci/reporters/index.ts +21 -0
- package/src/ci/reporters/json.ts +18 -0
- package/src/ci/reporters/junit.ts +61 -0
- package/src/ci/runner.ts +208 -0
- package/src/ci/test-discovery.ts +16 -0
- package/src/ci/test-loader.ts +187 -0
- package/src/ci/test-registry.ts +62 -0
- package/src/ci/trace-schema.ts +96 -0
- package/src/ci/trace-writer.ts +107 -0
- package/src/ci/types.ts +115 -0
- package/src/ci/upload-client.ts +300 -0
- package/src/cli.ts +811 -0
- package/src/core/agent-state.ts +162 -0
- package/src/core/judge-utils.ts +232 -0
- package/src/core/registry.ts +92 -0
- package/src/dashboard-server.ts +2047 -0
- package/src/execution/tool-runner.ts +352 -0
- package/src/html/dashboard.html +2218 -0
- package/src/http.ts +13 -0
- package/src/index.ts +138 -0
- package/src/interceptors/ai-interceptor.ts +798 -0
- package/src/interceptors/db-auto.ts +243 -0
- package/src/interceptors/db.ts +156 -0
- package/src/interceptors/http.ts +393 -0
- package/src/interceptors/side-effects.ts +83 -0
- package/src/interceptors/telemetry-push.ts +537 -0
- package/src/interceptors/tool.ts +287 -0
- package/src/interceptors/workflow-ai.ts +419 -0
- package/src/internals/conditional-recorder.ts +63 -0
- package/src/internals/mock-resolver.ts +492 -0
- package/src/matchers/index.ts +824 -0
- package/src/observability.ts +501 -0
- package/src/portal-executor.ts +355 -0
- package/src/portal-server.ts +304 -0
- package/src/proxy/llm-capture.ts +301 -0
- package/src/reporter.ts +81 -0
- package/src/runWorkflowSubprocess.ts +74 -0
- package/src/runner.ts +178 -0
- package/src/socket-connector.ts +117 -0
- package/src/telemetry-batcher.ts +191 -0
- package/src/test-setup.ts +16 -0
- package/src/tool-registry.ts +94 -0
- package/src/tool-runner-worker.ts +244 -0
- package/src/trace-adapter/context.ts +156 -0
- package/src/tracing.ts +62 -0
- package/src/trigger-executor.ts +171 -0
- package/src/types/agent.d.ts +63 -0
- package/src/types/expect.d.ts +81 -0
- package/src/types/modules.d.ts +2 -0
- package/src/types/portal.ts +69 -0
- package/src/utils/debug.ts +8 -0
- package/src/utils/license-error.ts +43 -0
- package/src/utils/redact.ts +25 -0
- package/src/workflow-runner-worker.ts +386 -0
- 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
|
+
}
|