elasticdash-test 0.1.17 → 0.1.18-alpha-2
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/dist/capture/event.d.ts +5 -1
- package/dist/capture/event.d.ts.map +1 -1
- package/dist/cli.js +100 -0
- package/dist/cli.js.map +1 -1
- package/dist/evaluators/llm-judge.js +17 -14
- package/dist/evaluators/types.d.ts +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 +270 -0
- package/dist/execution/tool-runner.js.map +1 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +2 -0
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +4400 -2665
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/ai-interceptor.d.ts.map +1 -1
- package/dist/interceptors/ai-interceptor.js +97 -4
- package/dist/interceptors/ai-interceptor.js.map +1 -1
- package/dist/interceptors/db-auto.d.ts.map +1 -1
- package/dist/interceptors/db-auto.js +116 -24
- package/dist/interceptors/db-auto.js.map +1 -1
- package/dist/interceptors/db.d.ts +5 -0
- package/dist/interceptors/db.d.ts.map +1 -1
- package/dist/interceptors/db.js +93 -15
- package/dist/interceptors/db.js.map +1 -1
- package/dist/interceptors/http.d.ts.map +1 -1
- package/dist/interceptors/http.js +125 -93
- package/dist/interceptors/http.js.map +1 -1
- package/dist/interceptors/telemetry-push.d.ts +15 -0
- package/dist/interceptors/telemetry-push.d.ts.map +1 -1
- package/dist/interceptors/telemetry-push.js +96 -13
- package/dist/interceptors/telemetry-push.js.map +1 -1
- package/dist/interceptors/tool.d.ts.map +1 -1
- package/dist/interceptors/tool.js +42 -5
- package/dist/interceptors/tool.js.map +1 -1
- package/dist/interceptors/workflow-ai.d.ts.map +1 -1
- package/dist/interceptors/workflow-ai.js +46 -2
- package/dist/interceptors/workflow-ai.js.map +1 -1
- package/dist/observability.d.ts +69 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +246 -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 +304 -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 +265 -0
- package/dist/portal-server.js.map +1 -0
- package/dist/socket-connector.d.ts +23 -0
- package/dist/socket-connector.d.ts.map +1 -0
- package/dist/socket-connector.js +102 -0
- package/dist/socket-connector.js.map +1 -0
- package/dist/telemetry-batcher.d.ts +43 -0
- package/dist/telemetry-batcher.d.ts.map +1 -0
- package/dist/telemetry-batcher.js +111 -0
- package/dist/telemetry-batcher.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 +83 -0
- package/dist/trigger-executor.js.map +1 -0
- package/dist/types/portal.d.ts +64 -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/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/package.json +10 -1
- package/src/capture/event.ts +5 -1
- package/src/cli.ts +109 -0
- package/src/execution/tool-runner.ts +304 -0
- package/src/http.ts +2 -0
- package/src/index.ts +18 -0
- package/src/interceptors/ai-interceptor.ts +110 -4
- package/src/interceptors/db-auto.ts +121 -25
- package/src/interceptors/db.ts +92 -17
- package/src/interceptors/http.ts +145 -107
- package/src/interceptors/telemetry-push.ts +113 -13
- package/src/interceptors/tool.ts +42 -5
- package/src/interceptors/workflow-ai.ts +49 -2
- package/src/observability.ts +286 -0
- package/src/portal-executor.ts +335 -0
- package/src/portal-server.ts +290 -0
- package/src/socket-connector.ts +115 -0
- package/src/telemetry-batcher.ts +143 -0
- package/src/trigger-executor.ts +121 -0
- package/src/types/portal.ts +67 -0
- package/src/utils/debug.ts +8 -0
- package/src/utils/redact.ts +25 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { executePortalTask, checkToolAvailability, checkAIAvailability } from './portal-executor.js';
|
|
2
|
+
import { scanTools } from './execution/tool-runner.js';
|
|
3
|
+
import { debugLog } from './utils/debug.js';
|
|
4
|
+
/**
|
|
5
|
+
* Executes a trigger received from the backend's event batch response.
|
|
6
|
+
*
|
|
7
|
+
* For each step:
|
|
8
|
+
* 1. Pre-validates availability (tool exists? API key set?)
|
|
9
|
+
* 2. If unavailable: reports `available: false` with reason, skips execution
|
|
10
|
+
* 3. If available: re-executes `runCount` times, collects results
|
|
11
|
+
* 4. POSTs all results to `POST /api/observability/triggers/:triggerId/results`
|
|
12
|
+
*/
|
|
13
|
+
export async function executeTrigger(serverUrl, apiKey, trigger) {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const tools = scanTools(cwd);
|
|
16
|
+
debugLog(`[elasticdash] Executing trigger ${trigger.triggerId}: ${trigger.steps.length} steps × ${trigger.runCount} runs`);
|
|
17
|
+
const stepResults = [];
|
|
18
|
+
for (const step of trigger.steps) {
|
|
19
|
+
// Pre-validate availability
|
|
20
|
+
const availability = step.eventType === 'ai'
|
|
21
|
+
? checkAIAvailability(step.provider, step.model ?? step.eventName)
|
|
22
|
+
: checkToolAvailability(step.eventName, cwd, tools);
|
|
23
|
+
if (!availability.available) {
|
|
24
|
+
debugLog(`[elasticdash] Trigger ${trigger.triggerId} step=${step.eventName} unavailable: ${availability.reason}`);
|
|
25
|
+
stepResults.push({
|
|
26
|
+
originalEventDbId: step.originalEventDbId,
|
|
27
|
+
eventType: step.eventType,
|
|
28
|
+
eventName: step.eventName,
|
|
29
|
+
available: false,
|
|
30
|
+
unavailableReason: availability.reason,
|
|
31
|
+
runs: [],
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
// Execute runs
|
|
36
|
+
const runs = [];
|
|
37
|
+
for (let i = 0; i < trigger.runCount; i++) {
|
|
38
|
+
const result = await executePortalTask({
|
|
39
|
+
taskId: `trigger-${trigger.triggerId}-${step.eventName}-${i}`,
|
|
40
|
+
type: step.eventType === 'ai' ? 'ai' : 'tool',
|
|
41
|
+
name: step.eventName,
|
|
42
|
+
input: step.input,
|
|
43
|
+
model: step.eventType === 'ai' ? (step.model ?? step.eventName) : undefined,
|
|
44
|
+
provider: step.provider,
|
|
45
|
+
}, cwd, tools);
|
|
46
|
+
runs.push({
|
|
47
|
+
runIndex: i,
|
|
48
|
+
input: step.input,
|
|
49
|
+
output: result.output,
|
|
50
|
+
durationMs: result.durationMs,
|
|
51
|
+
error: result.error,
|
|
52
|
+
usageInputTokens: result.usage?.inputTokens,
|
|
53
|
+
usageOutputTokens: result.usage?.outputTokens,
|
|
54
|
+
usageTotalTokens: result.usage?.totalTokens,
|
|
55
|
+
});
|
|
56
|
+
debugLog(`[elasticdash] Trigger ${trigger.triggerId} step=${step.eventName} run=${i} ok=${result.ok}`);
|
|
57
|
+
}
|
|
58
|
+
stepResults.push({
|
|
59
|
+
originalEventDbId: step.originalEventDbId,
|
|
60
|
+
eventType: step.eventType,
|
|
61
|
+
eventName: step.eventName,
|
|
62
|
+
available: true,
|
|
63
|
+
runs,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// POST results to backend
|
|
67
|
+
const url = `${serverUrl.replace(/\/$/, '')}/api/observability/triggers/${trigger.triggerId}/results`;
|
|
68
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
69
|
+
if (apiKey)
|
|
70
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetch(url, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers,
|
|
75
|
+
body: JSON.stringify({ steps: stepResults }),
|
|
76
|
+
});
|
|
77
|
+
debugLog(`[elasticdash] Trigger ${trigger.triggerId} results posted (status ${res.status})`);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
debugLog(`[elasticdash] Trigger ${trigger.triggerId} results POST failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=trigger-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger-executor.js","sourceRoot":"","sources":["../src/trigger-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface PortalTask {
|
|
2
|
+
/** Unique task ID assigned by the backend */
|
|
3
|
+
taskId: string;
|
|
4
|
+
/** What to rerun */
|
|
5
|
+
type: 'tool' | 'ai';
|
|
6
|
+
/** Tool name or model name */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Tool arguments or LLM prompt/messages */
|
|
9
|
+
input: unknown;
|
|
10
|
+
/** Model name (for AI tasks) */
|
|
11
|
+
model?: string;
|
|
12
|
+
/** LLM provider: openai, anthropic, gemini, grok, etc. */
|
|
13
|
+
provider?: string;
|
|
14
|
+
/** LLM generation parameters */
|
|
15
|
+
modelParameters?: {
|
|
16
|
+
temperature?: number;
|
|
17
|
+
max_tokens?: number;
|
|
18
|
+
};
|
|
19
|
+
/** Passthrough metadata (test group ID, expectation IDs, etc.) */
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export interface PortalTaskResult {
|
|
23
|
+
taskId: string;
|
|
24
|
+
ok: boolean;
|
|
25
|
+
output: unknown;
|
|
26
|
+
error?: string;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
usage?: {
|
|
29
|
+
inputTokens?: number;
|
|
30
|
+
outputTokens?: number;
|
|
31
|
+
totalTokens?: number;
|
|
32
|
+
};
|
|
33
|
+
/** Echoed from the original task */
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
export interface PortalServerOptions {
|
|
37
|
+
/** Port to listen on (default 4574) */
|
|
38
|
+
port?: number;
|
|
39
|
+
/** Backend URL to POST results to */
|
|
40
|
+
backendUrl: string;
|
|
41
|
+
/** Auth token for incoming and outgoing requests */
|
|
42
|
+
apiKey?: string;
|
|
43
|
+
/** Project root directory (default process.cwd()) */
|
|
44
|
+
cwd?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Allowed origin domains that may send requests to this portal.
|
|
47
|
+
* By default only the `backendUrl` domain and localhost are allowed.
|
|
48
|
+
* Provide additional origins (e.g. 'https://app.elasticdash.com') to extend.
|
|
49
|
+
*/
|
|
50
|
+
allowedOrigins?: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface PortalServerHandle {
|
|
53
|
+
port: number;
|
|
54
|
+
url: string;
|
|
55
|
+
close: () => Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export interface PortalStatus {
|
|
58
|
+
ok: boolean;
|
|
59
|
+
queueLength: number;
|
|
60
|
+
processing: string | null;
|
|
61
|
+
completed: number;
|
|
62
|
+
failed: number;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=portal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/types/portal.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,oBAAoB;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,KAAK,EAAE,OAAO,CAAA;IACd,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,eAAe,CAAC,EAAE;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.js","sourceRoot":"","sources":["../../src/types/portal.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAEA,qGAAqG;AACrG,wBAAgB,QAAQ,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAIjD"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const DEBUG_KEY = 'ELASTICDASH_DEBUG';
|
|
2
|
+
/** Log only when ELASTICDASH_DEBUG=1 is set. Drop-in replacement for console.log in interceptors. */
|
|
3
|
+
export function debugLog(...args) {
|
|
4
|
+
if (typeof process !== 'undefined' && process.env?.[DEBUG_KEY] === '1') {
|
|
5
|
+
console.log(...args);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,mBAAmB,CAAA;AAErC,qGAAqG;AACrG,MAAM,UAAU,QAAQ,CAAC,GAAG,IAAe;IACzC,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,GAAG,EAAE,CAAC;QACvE,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;IACtB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-clones a value and replaces any object property whose key matches
|
|
3
|
+
* one of `keys` (case-insensitive) with "[REDACTED]".
|
|
4
|
+
* Returns the original value when `keys` is empty.
|
|
5
|
+
*/
|
|
6
|
+
export declare function redactPayload(value: unknown, keys: string[]): unknown;
|
|
7
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/utils/redact.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAIrE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-clones a value and replaces any object property whose key matches
|
|
3
|
+
* one of `keys` (case-insensitive) with "[REDACTED]".
|
|
4
|
+
* Returns the original value when `keys` is empty.
|
|
5
|
+
*/
|
|
6
|
+
export function redactPayload(value, keys) {
|
|
7
|
+
if (keys.length === 0)
|
|
8
|
+
return value;
|
|
9
|
+
const lowerKeys = new Set(keys.map((k) => k.toLowerCase()));
|
|
10
|
+
return redact(value, lowerKeys);
|
|
11
|
+
}
|
|
12
|
+
function redact(value, keys) {
|
|
13
|
+
if (value === null || value === undefined)
|
|
14
|
+
return value;
|
|
15
|
+
if (typeof value !== 'object')
|
|
16
|
+
return value;
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return value.map((item) => redact(item, keys));
|
|
19
|
+
}
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [k, v] of Object.entries(value)) {
|
|
22
|
+
out[k] = keys.has(k.toLowerCase()) ? '[REDACTED]' : redact(v, keys);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/utils/redact.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,IAAc;IAC1D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACnC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IAC3D,OAAO,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,SAAS,MAAM,CAAC,KAAc,EAAE,IAAiB;IAC/C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE3C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,GAAG,GAA4B,EAAE,CAAA;IACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QACtE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IACrE,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elasticdash-test",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18-alpha-2",
|
|
4
4
|
"description": "AI-native test runner for ElasticDash workflow testing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
"./http": {
|
|
18
18
|
"types": "./dist/http.d.ts",
|
|
19
19
|
"default": "./dist/http.js"
|
|
20
|
+
},
|
|
21
|
+
"./observability": {
|
|
22
|
+
"types": "./dist/observability.d.ts",
|
|
23
|
+
"default": "./dist/observability.js"
|
|
24
|
+
},
|
|
25
|
+
"./portal": {
|
|
26
|
+
"types": "./dist/portal-server.d.ts",
|
|
27
|
+
"default": "./dist/portal-server.js"
|
|
20
28
|
}
|
|
21
29
|
},
|
|
22
30
|
"files": [
|
|
@@ -43,6 +51,7 @@
|
|
|
43
51
|
"expect": "^29.7.0",
|
|
44
52
|
"express": "^5.2.1",
|
|
45
53
|
"fast-glob": "^3.3.2",
|
|
54
|
+
"socket.io-client": "^4.8.3",
|
|
46
55
|
"tsx": "^4.15.6"
|
|
47
56
|
},
|
|
48
57
|
"devDependencies": {
|
package/src/capture/event.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type WorkflowEventType = 'ai' | 'tool' | 'http' | 'db' | 'side_effect'
|
|
1
|
+
export type WorkflowEventType = 'ai' | 'tool' | 'http' | 'db' | 'side_effect' | 'workflow'
|
|
2
2
|
|
|
3
3
|
export interface WorkflowEvent {
|
|
4
4
|
id: number
|
|
@@ -18,6 +18,10 @@ export interface WorkflowEvent {
|
|
|
18
18
|
streamed?: boolean
|
|
19
19
|
/** Raw buffered text of a streamed response (used for replay) */
|
|
20
20
|
streamRaw?: string
|
|
21
|
+
/** Schema version for forward compatibility (default 1) */
|
|
22
|
+
schemaVersion?: number
|
|
23
|
+
/** Optional request-level trace ID for grouping events within a session */
|
|
24
|
+
traceId?: string
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export interface WorkflowTrace {
|
package/src/cli.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { runFiles } from './runner.js'
|
|
|
12
12
|
import { reportResults } from './reporter.js'
|
|
13
13
|
import { startBrowserUiServer, type UiEvent } from './browser-ui.js'
|
|
14
14
|
import { startDashboardServer } from './dashboard-server.js'
|
|
15
|
+
import { initObservability, shutdownObservability } from './observability.js'
|
|
16
|
+
import { startPortalServer } from './portal-server.js'
|
|
15
17
|
|
|
16
18
|
function stripAnsi(input?: string): string | undefined {
|
|
17
19
|
if (!input) return input
|
|
@@ -306,6 +308,113 @@ async function bootstrap(): Promise<void> {
|
|
|
306
308
|
process.once('SIGTERM', cleanup)
|
|
307
309
|
})
|
|
308
310
|
|
|
311
|
+
// elasticdash observe
|
|
312
|
+
program
|
|
313
|
+
.command('observe')
|
|
314
|
+
.description('Start observability mode — stream trace events to ElasticDash backend')
|
|
315
|
+
.option('--server <url>', 'ElasticDash backend API URL', process.env.ELASTICDASH_API_URL)
|
|
316
|
+
.option('--api-key <key>', 'Project API key', process.env.ELASTICDASH_API_KEY)
|
|
317
|
+
.option('--service-id <id>', 'Service identifier', process.env.ELASTICDASH_SERVICE_ID)
|
|
318
|
+
.action(async (options: { server?: string; apiKey?: string; serviceId?: string }) => {
|
|
319
|
+
const serverUrl = options.server
|
|
320
|
+
if (!serverUrl) {
|
|
321
|
+
console.error('[elasticdash] Error: --server or ELASTICDASH_API_URL is required')
|
|
322
|
+
process.exit(1)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const handle = initObservability({
|
|
326
|
+
serverUrl,
|
|
327
|
+
apiKey: options.apiKey,
|
|
328
|
+
serviceId: options.serviceId,
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
console.log(`[elasticdash] Observability active`)
|
|
332
|
+
console.log(` Session ID : ${handle.sessionId}`)
|
|
333
|
+
console.log(` Server : ${serverUrl}`)
|
|
334
|
+
console.log(` Service : ${options.serviceId ?? process.env.ELASTICDASH_SERVICE_ID ?? 'unknown-service'}`)
|
|
335
|
+
console.log(`[elasticdash] Press Ctrl+C to stop`)
|
|
336
|
+
|
|
337
|
+
let isShuttingDown = false
|
|
338
|
+
const cleanup = async () => {
|
|
339
|
+
if (isShuttingDown) {
|
|
340
|
+
process.exit(1)
|
|
341
|
+
}
|
|
342
|
+
isShuttingDown = true
|
|
343
|
+
console.log('\n[elasticdash] Shutting down observability...')
|
|
344
|
+
await shutdownObservability()
|
|
345
|
+
process.exit(0)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
process.once('SIGINT', cleanup)
|
|
349
|
+
process.once('SIGTERM', cleanup)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
// elasticdash portal
|
|
353
|
+
program
|
|
354
|
+
.command('portal')
|
|
355
|
+
.description('Start a portal server to receive and execute rerun tasks from ElasticDash backend')
|
|
356
|
+
.option('--server <url>', 'ElasticDash backend API URL to POST results to', process.env.ELASTICDASH_API_URL)
|
|
357
|
+
.option('--api-key <key>', 'Project API key', process.env.ELASTICDASH_API_KEY)
|
|
358
|
+
.option('--port <port>', 'Portal server port', (v) => Number(v), process.env.ELASTICDASH_PORTAL_PORT ? Number(process.env.ELASTICDASH_PORTAL_PORT) : 4574)
|
|
359
|
+
.option('--allowed-origins <origins>', 'Comma-separated list of additional allowed origin domains', process.env.ELASTICDASH_ALLOWED_ORIGINS)
|
|
360
|
+
.action(async (options: { server?: string; apiKey?: string; port: number; allowedOrigins?: string }) => {
|
|
361
|
+
const backendUrl = options.server
|
|
362
|
+
if (!backendUrl) {
|
|
363
|
+
console.error('[elasticdash] Error: --server or ELASTICDASH_API_URL is required')
|
|
364
|
+
process.exit(1)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const allowedOrigins = options.allowedOrigins
|
|
368
|
+
? options.allowedOrigins.split(',').map(s => s.trim()).filter(Boolean)
|
|
369
|
+
: undefined
|
|
370
|
+
|
|
371
|
+
const handle = await startPortalServer({
|
|
372
|
+
port: options.port,
|
|
373
|
+
backendUrl,
|
|
374
|
+
apiKey: options.apiKey,
|
|
375
|
+
cwd,
|
|
376
|
+
allowedOrigins,
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
console.log(`[elasticdash] Portal server running`)
|
|
380
|
+
console.log(` URL : ${handle.url}`)
|
|
381
|
+
console.log(` Backend : ${backendUrl}`)
|
|
382
|
+
console.log(` Port : ${handle.port}`)
|
|
383
|
+
console.log(`[elasticdash] Waiting for tasks from backend... Press Ctrl+C to stop`)
|
|
384
|
+
|
|
385
|
+
// Register with backend
|
|
386
|
+
try {
|
|
387
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
|
388
|
+
if (options.apiKey) headers['Authorization'] = `Bearer ${options.apiKey}`
|
|
389
|
+
const res = await fetch(`${backendUrl}/api/portal/register`, {
|
|
390
|
+
method: 'POST',
|
|
391
|
+
headers,
|
|
392
|
+
body: JSON.stringify({ portalUrl: handle.url }),
|
|
393
|
+
})
|
|
394
|
+
if (res.ok) {
|
|
395
|
+
console.log(`[elasticdash] Registered with backend`)
|
|
396
|
+
} else {
|
|
397
|
+
console.warn(`[elasticdash] Backend registration returned ${res.status} — portal will still accept tasks directly`)
|
|
398
|
+
}
|
|
399
|
+
} catch {
|
|
400
|
+
console.warn(`[elasticdash] Could not register with backend — portal will still accept tasks directly`)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let isShuttingDown = false
|
|
404
|
+
const cleanup = async () => {
|
|
405
|
+
if (isShuttingDown) {
|
|
406
|
+
process.exit(1)
|
|
407
|
+
}
|
|
408
|
+
isShuttingDown = true
|
|
409
|
+
console.log('\n[elasticdash] Shutting down portal...')
|
|
410
|
+
await handle.close()
|
|
411
|
+
process.exit(0)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
process.once('SIGINT', cleanup)
|
|
415
|
+
process.once('SIGTERM', cleanup)
|
|
416
|
+
})
|
|
417
|
+
|
|
309
418
|
await program.parseAsync(process.argv)
|
|
310
419
|
}
|
|
311
420
|
|