@vostride/agent-qa-analytics 0.0.1 → 0.2.1
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.md +105 -0
- package/NOTICE.md +11 -0
- package/dist/index.cjs +961 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1772 -0
- package/dist/index.d.ts +1772 -0
- package/dist/index.js +893 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ANALYTICS_AGENT_PRODUCTS: () => ANALYTICS_AGENT_PRODUCTS,
|
|
24
|
+
ANALYTICS_CHAT_END_REASONS: () => ANALYTICS_CHAT_END_REASONS,
|
|
25
|
+
ANALYTICS_DAEMON_JOB_KINDS: () => ANALYTICS_DAEMON_JOB_KINDS,
|
|
26
|
+
ANALYTICS_DAEMON_JOB_OUTCOMES: () => ANALYTICS_DAEMON_JOB_OUTCOMES,
|
|
27
|
+
ANALYTICS_EMITTING_HOSTS: () => ANALYTICS_EMITTING_HOSTS,
|
|
28
|
+
ANALYTICS_EVENT_MEMBERS: () => ANALYTICS_EVENT_MEMBERS,
|
|
29
|
+
ANALYTICS_EVENT_NAMES: () => ANALYTICS_EVENT_NAMES,
|
|
30
|
+
ANALYTICS_EVENT_SCHEMA_BY_NAME: () => ANALYTICS_EVENT_SCHEMA_BY_NAME,
|
|
31
|
+
ANALYTICS_FAILURE_CATEGORIES: () => ANALYTICS_FAILURE_CATEGORIES,
|
|
32
|
+
ANALYTICS_INIT_MODES: () => ANALYTICS_INIT_MODES,
|
|
33
|
+
ANALYTICS_INIT_OUTCOMES: () => ANALYTICS_INIT_OUTCOMES,
|
|
34
|
+
ANALYTICS_KNOWN_ID_MARKER: () => ANALYTICS_KNOWN_ID_MARKER,
|
|
35
|
+
ANALYTICS_MCP_ERROR_CATEGORIES: () => ANALYTICS_MCP_ERROR_CATEGORIES,
|
|
36
|
+
ANALYTICS_MCP_SERVER_STATES: () => ANALYTICS_MCP_SERVER_STATES,
|
|
37
|
+
ANALYTICS_MCP_TOOL_NAMES: () => ANALYTICS_MCP_TOOL_NAMES,
|
|
38
|
+
ANALYTICS_MCP_TOOL_STATUSES: () => ANALYTICS_MCP_TOOL_STATUSES,
|
|
39
|
+
ANALYTICS_MCP_TRANSPORTS: () => ANALYTICS_MCP_TRANSPORTS,
|
|
40
|
+
ANALYTICS_PLATFORMS: () => ANALYTICS_PLATFORMS,
|
|
41
|
+
ANALYTICS_RUNNERS: () => ANALYTICS_RUNNERS,
|
|
42
|
+
ANALYTICS_RUNTIME_CONTEXTS: () => ANALYTICS_RUNTIME_CONTEXTS,
|
|
43
|
+
ANALYTICS_RUN_STATUSES: () => ANALYTICS_RUN_STATUSES,
|
|
44
|
+
ANALYTICS_SUITE_EXECUTION_MODES: () => ANALYTICS_SUITE_EXECUTION_MODES,
|
|
45
|
+
ANALYTICS_TRIGGER_SOURCES: () => ANALYTICS_TRIGGER_SOURCES,
|
|
46
|
+
ANALYTICS_USER_ID_PREFIX: () => ANALYTICS_USER_ID_PREFIX,
|
|
47
|
+
ANALYTICS_USER_ID_WORDS: () => ANALYTICS_USER_ID_WORDS,
|
|
48
|
+
ANALYTICS_VOCABULARY_META_KEY: () => ANALYTICS_VOCABULARY_META_KEY,
|
|
49
|
+
AgentQaAnalyticsEventSchema: () => AgentQaAnalyticsEventSchema,
|
|
50
|
+
RETIRED_V33_EVENT_NAMES: () => RETIRED_V33_EVENT_NAMES,
|
|
51
|
+
analyticsRunId: () => analyticsRunId,
|
|
52
|
+
analyticsSuiteId: () => analyticsSuiteId,
|
|
53
|
+
analyticsTestId: () => analyticsTestId,
|
|
54
|
+
assertNoUnconstrainedStrings: () => assertNoUnconstrainedStrings,
|
|
55
|
+
composeAnalytics: () => composeAnalytics,
|
|
56
|
+
createAnalyticsPort: () => createAnalyticsPort,
|
|
57
|
+
createAnalyticsRunSubscriber: () => createAnalyticsRunSubscriber,
|
|
58
|
+
createPosthogTransport: () => createPosthogTransport,
|
|
59
|
+
generateAnalyticsUserId: () => generateAnalyticsUserId,
|
|
60
|
+
getAnalyticsIdentityPath: () => getAnalyticsIdentityPath,
|
|
61
|
+
maskEventForSend: () => maskEventForSend,
|
|
62
|
+
readAnalyticsIdentity: () => readAnalyticsIdentity,
|
|
63
|
+
resolveAnalyticsAgentProduct: () => resolveAnalyticsAgentProduct,
|
|
64
|
+
resolveAnalyticsIdentity: () => resolveAnalyticsIdentity
|
|
65
|
+
});
|
|
66
|
+
module.exports = __toCommonJS(index_exports);
|
|
67
|
+
|
|
68
|
+
// src/port.ts
|
|
69
|
+
var DEFAULT_ANALYTICS_SHUTDOWN_TIMEOUT_MS = 2e3;
|
|
70
|
+
var BestEffortAnalyticsPort = class {
|
|
71
|
+
transport;
|
|
72
|
+
identity;
|
|
73
|
+
shutdownTimeoutMs;
|
|
74
|
+
inFlight = [];
|
|
75
|
+
capturedSinceFlush = 0;
|
|
76
|
+
sending = /* @__PURE__ */ new Set();
|
|
77
|
+
constructor(options) {
|
|
78
|
+
this.transport = options.transport;
|
|
79
|
+
this.identity = options.identity;
|
|
80
|
+
this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? DEFAULT_ANALYTICS_SHUTDOWN_TIMEOUT_MS;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* There is NO try/catch here, and the absence is deliberate — measured, not assumed.
|
|
84
|
+
*
|
|
85
|
+
* 2026-08-17 (Phase 398 plan 03, mutation leg M6). This method was first written with the v1
|
|
86
|
+
* module's `try { ... } catch {}` wrapper around the body. Deleting that wrapper outright left
|
|
87
|
+
* every cell green, including the one named "never throws into the host", because there is no
|
|
88
|
+
* synchronous throw for it to catch: an `async` function body's throw — including one out of the
|
|
89
|
+
* identity resolver, the only caller-supplied code reachable from here — becomes a REJECTED
|
|
90
|
+
* PROMISE, never a synchronous exception. The wrapper read like the boundary and guarded nothing.
|
|
91
|
+
*
|
|
92
|
+
* The real boundary is the `.catch` below, attached in the same tick the promise is created so a
|
|
93
|
+
* rejection cannot become an unhandled rejection even if `flush()` is never called — which is the
|
|
94
|
+
* normal case for a host that emits on a path it does not await. Leg M8 removes that `.catch` and
|
|
95
|
+
* reds; that is the guard this method actually has.
|
|
96
|
+
*/
|
|
97
|
+
capture(event) {
|
|
98
|
+
const transport = this.transport;
|
|
99
|
+
if (transport === void 0) return;
|
|
100
|
+
this.capturedSinceFlush += 1;
|
|
101
|
+
this.inFlight.push(
|
|
102
|
+
(async () => {
|
|
103
|
+
const identity = await this.identity();
|
|
104
|
+
await transport.capture({
|
|
105
|
+
distinctId: identity.distinctId,
|
|
106
|
+
...identity.isInternal === true ? { isInternal: true } : {},
|
|
107
|
+
event
|
|
108
|
+
});
|
|
109
|
+
})().catch(() => {
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* EDGE ANLY-01/empty. A flush with nothing captured touches the transport at ALL — not "makes no
|
|
115
|
+
* network call", but makes no call. The distinction matters because `flush` on the PostHog
|
|
116
|
+
* transport is a send, so a host that flushes on every command return would otherwise poke the
|
|
117
|
+
* wire once per invocation whether or not anything happened. Zero captures in, zero calls out,
|
|
118
|
+
* and the promise still resolves.
|
|
119
|
+
*
|
|
120
|
+
* 2026-10-21 (447-07, D-20): flush awaits the local captures and STARTS the send without awaiting
|
|
121
|
+
* it, so a stalled network never holds a run's verdict or a command's return; `shutdown` waits
|
|
122
|
+
* for the started sends inside its bound.
|
|
123
|
+
*/
|
|
124
|
+
async flush() {
|
|
125
|
+
const transport = this.transport;
|
|
126
|
+
if (transport === void 0) return;
|
|
127
|
+
try {
|
|
128
|
+
const pending = this.inFlight;
|
|
129
|
+
const captured = this.capturedSinceFlush;
|
|
130
|
+
this.inFlight = [];
|
|
131
|
+
this.capturedSinceFlush = 0;
|
|
132
|
+
await Promise.all(pending);
|
|
133
|
+
if (captured === 0) return;
|
|
134
|
+
const send = (async () => {
|
|
135
|
+
await transport.flush();
|
|
136
|
+
})().catch(() => {
|
|
137
|
+
});
|
|
138
|
+
this.sending.add(send);
|
|
139
|
+
void send.finally(() => this.sending.delete(send));
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Shutdown always reaches the transport, even with an empty queue: it releases whatever the
|
|
145
|
+
* transport holds (a PostHog client owns timers), and "release nothing" is cheap while "leak a
|
|
146
|
+
* timer because the session happened to be silent" keeps a process alive.
|
|
147
|
+
*/
|
|
148
|
+
async shutdown() {
|
|
149
|
+
const transport = this.transport;
|
|
150
|
+
if (transport === void 0) return;
|
|
151
|
+
let timer;
|
|
152
|
+
try {
|
|
153
|
+
await Promise.race([
|
|
154
|
+
(async () => {
|
|
155
|
+
await this.flush();
|
|
156
|
+
await Promise.all([...this.sending]);
|
|
157
|
+
await transport.shutdown();
|
|
158
|
+
})(),
|
|
159
|
+
new Promise((resolve) => {
|
|
160
|
+
timer = setTimeout(resolve, this.shutdownTimeoutMs);
|
|
161
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
162
|
+
})
|
|
163
|
+
]);
|
|
164
|
+
} catch {
|
|
165
|
+
} finally {
|
|
166
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
function createAnalyticsPort(options) {
|
|
171
|
+
return new BestEffortAnalyticsPort(options);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/transports.ts
|
|
175
|
+
var import_posthog_node = require("posthog-node");
|
|
176
|
+
|
|
177
|
+
// src/masking-seam.ts
|
|
178
|
+
function maskEventForSend(payload, context) {
|
|
179
|
+
if (context === void 0) return payload;
|
|
180
|
+
return {
|
|
181
|
+
...payload,
|
|
182
|
+
// The distinct id IS a masked surface: it is the one caller-supplied string on the envelope, and
|
|
183
|
+
// if a registered secret is what a host handed in as an identity, that is precisely a leak.
|
|
184
|
+
distinctId: context.mask(payload.distinctId),
|
|
185
|
+
event: {
|
|
186
|
+
name: payload.event.name,
|
|
187
|
+
properties: maskLeaves(payload.event.properties, context)
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function maskLeaves(value, context) {
|
|
192
|
+
if (typeof value === "string") return context.mask(value);
|
|
193
|
+
if (Array.isArray(value)) return value.map((entry) => maskLeaves(entry, context));
|
|
194
|
+
if (value !== null && typeof value === "object" && isPlainObject(value)) {
|
|
195
|
+
const out = {};
|
|
196
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
197
|
+
out[key] = maskLeaves(entry, context);
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
function isPlainObject(value) {
|
|
204
|
+
const prototype = Object.getPrototypeOf(value);
|
|
205
|
+
return prototype === Object.prototype || prototype === null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/transports.ts
|
|
209
|
+
var PosthogTransport = class {
|
|
210
|
+
client;
|
|
211
|
+
maskingContext;
|
|
212
|
+
constructor(options) {
|
|
213
|
+
this.maskingContext = options.maskingContext;
|
|
214
|
+
this.client = options.client ?? new import_posthog_node.PostHog(options.projectKey, {
|
|
215
|
+
host: options.host,
|
|
216
|
+
disableGeoip: options.disableGeoip ?? false,
|
|
217
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* THE SEND CHOKE POINT. Masking is the first thing that happens here and it happens exactly once
|
|
222
|
+
* (a cell counts the call sites in shipped source and refuses any number but one).
|
|
223
|
+
*
|
|
224
|
+
* `flush`/`shutdown` are not second seams: everything they drain arrived through this method, so
|
|
225
|
+
* masking here ranges the whole queue. `is_internal` is added AFTER the mask because it is a
|
|
226
|
+
* boolean this file writes — there is no caller string in it to mask. It is an event property,
|
|
227
|
+
* sent only when true (447-07, D-13).
|
|
228
|
+
*/
|
|
229
|
+
capture(payload) {
|
|
230
|
+
let masked;
|
|
231
|
+
try {
|
|
232
|
+
masked = maskEventForSend(payload, this.maskingContext);
|
|
233
|
+
} catch {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const properties = { ...masked.event.properties };
|
|
237
|
+
if (masked.isInternal === true) {
|
|
238
|
+
properties.is_internal = true;
|
|
239
|
+
}
|
|
240
|
+
this.client.capture({
|
|
241
|
+
distinctId: masked.distinctId,
|
|
242
|
+
event: masked.event.name,
|
|
243
|
+
properties
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async flush() {
|
|
247
|
+
await this.client.flush();
|
|
248
|
+
}
|
|
249
|
+
async shutdown() {
|
|
250
|
+
await this.client.shutdown();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
function createPosthogTransport(options) {
|
|
254
|
+
return new PosthogTransport(options);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/posthog-project.ts
|
|
258
|
+
var AGENT_QA_POSTHOG_KEY = "phc_siDVZ3DEoSCiBSocn2GZMRhx5UcRwekJDVtap5Zteii3";
|
|
259
|
+
var AGENT_QA_POSTHOG_HOST = "https://us.i.posthog.com";
|
|
260
|
+
|
|
261
|
+
// src/compose.ts
|
|
262
|
+
function composeAnalytics(options = {}) {
|
|
263
|
+
const optedOut = options.config?.analytics?.privacy === true;
|
|
264
|
+
const optOutReasons = optedOut ? ["config-privacy"] : [];
|
|
265
|
+
const { transport, transportKind } = optedOut ? NO_TRANSPORT : enabledTransport(options);
|
|
266
|
+
return {
|
|
267
|
+
port: createAnalyticsPort({
|
|
268
|
+
transport,
|
|
269
|
+
identity: options.identity ?? noIdentity
|
|
270
|
+
}),
|
|
271
|
+
transport,
|
|
272
|
+
optedOut,
|
|
273
|
+
optOutReasons,
|
|
274
|
+
transportKind
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
var noIdentity = () => Promise.reject(new Error("no analytics identity was supplied"));
|
|
278
|
+
var NO_TRANSPORT = { transport: void 0, transportKind: "none" };
|
|
279
|
+
function enabledTransport(options) {
|
|
280
|
+
if (options.transportOverride) {
|
|
281
|
+
return { transport: options.transportOverride, transportKind: "posthog" };
|
|
282
|
+
}
|
|
283
|
+
const projectKey = (options.projectKey ?? AGENT_QA_POSTHOG_KEY).trim();
|
|
284
|
+
if (projectKey === "") return NO_TRANSPORT;
|
|
285
|
+
const factory = options.posthogTransportFactory ?? createPosthogTransport;
|
|
286
|
+
return {
|
|
287
|
+
transport: factory({
|
|
288
|
+
projectKey,
|
|
289
|
+
host: options.projectHost ?? AGENT_QA_POSTHOG_HOST,
|
|
290
|
+
disableGeoip: false,
|
|
291
|
+
...options.maskingContext ? { maskingContext: options.maskingContext } : {}
|
|
292
|
+
}),
|
|
293
|
+
transportKind: "posthog"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/events.ts
|
|
298
|
+
var import_agent_qa_ids = require("@vostride/agent-qa-ids");
|
|
299
|
+
var import_agent_qa_mcp = require("@vostride/agent-qa-mcp");
|
|
300
|
+
var import_zod = require("zod");
|
|
301
|
+
var ANALYTICS_VOCABULARY_META_KEY = "analyticsVocabulary";
|
|
302
|
+
var ANALYTICS_KNOWN_ID_MARKER = "known-id";
|
|
303
|
+
function knownId(codec, accepts) {
|
|
304
|
+
return import_zod.z.string().refine(accepts, { message: `expected a ${codec} id` }).meta({ [ANALYTICS_VOCABULARY_META_KEY]: ANALYTICS_KNOWN_ID_MARKER, analyticsCodec: codec });
|
|
305
|
+
}
|
|
306
|
+
var SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
307
|
+
var AgentQaVersionSchema = import_zod.z.string().regex(SEMVER_PATTERN, "expected a semver version").meta({ [ANALYTICS_VOCABULARY_META_KEY]: ANALYTICS_KNOWN_ID_MARKER, analyticsCodec: "semver" });
|
|
308
|
+
function analyticsRunId(value) {
|
|
309
|
+
return typeof value === "string" && (0, import_agent_qa_ids.isCanonicalRunId)(value) ? value : void 0;
|
|
310
|
+
}
|
|
311
|
+
function analyticsTestId(value) {
|
|
312
|
+
return typeof value === "string" && (0, import_agent_qa_ids.isCanonicalTestId)(value) ? value : void 0;
|
|
313
|
+
}
|
|
314
|
+
function analyticsSuiteId(value) {
|
|
315
|
+
return typeof value === "string" && (0, import_agent_qa_ids.isCanonicalSuiteId)(value) ? value : void 0;
|
|
316
|
+
}
|
|
317
|
+
var RunIdSchema = knownId("run", import_agent_qa_ids.isCanonicalRunId);
|
|
318
|
+
var TestIdSchema = knownId("test", import_agent_qa_ids.isCanonicalTestId);
|
|
319
|
+
var SuiteIdSchema = knownId("suite", import_agent_qa_ids.isCanonicalSuiteId);
|
|
320
|
+
var NonNegativeInteger = import_zod.z.number().int().min(0);
|
|
321
|
+
var NonNegativeNumber = import_zod.z.number().min(0);
|
|
322
|
+
var ANALYTICS_EMITTING_HOSTS = ["cli", "chat", "mcp", "daemon"];
|
|
323
|
+
var ANALYTICS_RUNTIME_CONTEXTS = ["user", "ci"];
|
|
324
|
+
var ANALYTICS_AGENT_PRODUCTS = [
|
|
325
|
+
"claude_code",
|
|
326
|
+
"cursor",
|
|
327
|
+
"gemini_cli",
|
|
328
|
+
"augment",
|
|
329
|
+
"goose",
|
|
330
|
+
"opencode",
|
|
331
|
+
"codex",
|
|
332
|
+
"cline",
|
|
333
|
+
"amp",
|
|
334
|
+
"unknown"
|
|
335
|
+
];
|
|
336
|
+
var ANALYTICS_TRIGGER_SOURCES = ["cli", "chat", "mcp", "daemon", "unknown"];
|
|
337
|
+
var ANALYTICS_RUNNERS = ["local", "browserstack", "unknown"];
|
|
338
|
+
var ANALYTICS_PLATFORMS = ["web", "android", "ios", "unknown"];
|
|
339
|
+
var ANALYTICS_RUN_STATUSES = [
|
|
340
|
+
"passed",
|
|
341
|
+
"failed",
|
|
342
|
+
"skipped",
|
|
343
|
+
"cancelled",
|
|
344
|
+
"timeout",
|
|
345
|
+
"unknown"
|
|
346
|
+
];
|
|
347
|
+
var ANALYTICS_SUITE_EXECUTION_MODES = ["sequential", "parallel", "unknown"];
|
|
348
|
+
var ANALYTICS_FAILURE_CATEGORIES = [
|
|
349
|
+
"assertion",
|
|
350
|
+
"timeout",
|
|
351
|
+
"cancelled",
|
|
352
|
+
"setup",
|
|
353
|
+
"hook",
|
|
354
|
+
"appium",
|
|
355
|
+
"browser_install",
|
|
356
|
+
"llm_context",
|
|
357
|
+
"llm_provider",
|
|
358
|
+
"memory",
|
|
359
|
+
"framework",
|
|
360
|
+
"unknown"
|
|
361
|
+
];
|
|
362
|
+
var ANALYTICS_MCP_SERVER_STATES = ["started", "stopped"];
|
|
363
|
+
var ANALYTICS_MCP_TRANSPORTS = ["stdio", "http"];
|
|
364
|
+
var ANALYTICS_MCP_TOOL_STATUSES = ["success", "error"];
|
|
365
|
+
var ANALYTICS_MCP_ERROR_CATEGORIES = [
|
|
366
|
+
"validation",
|
|
367
|
+
"scope_denied",
|
|
368
|
+
"unknown_tool",
|
|
369
|
+
"tool_error",
|
|
370
|
+
"unknown"
|
|
371
|
+
];
|
|
372
|
+
var ANALYTICS_DAEMON_JOB_KINDS = ["run", "suite", "recovery", "unknown"];
|
|
373
|
+
var ANALYTICS_DAEMON_JOB_OUTCOMES = [
|
|
374
|
+
"completed",
|
|
375
|
+
"failed",
|
|
376
|
+
"cancelled",
|
|
377
|
+
"abandoned",
|
|
378
|
+
"unknown"
|
|
379
|
+
];
|
|
380
|
+
var ANALYTICS_INIT_MODES = ["interactive", "agent", "scaffold"];
|
|
381
|
+
var ANALYTICS_INIT_OUTCOMES = ["created", "updated", "unchanged", "failed"];
|
|
382
|
+
var ANALYTICS_CHAT_END_REASONS = ["exit", "interrupt", "error", "unknown"];
|
|
383
|
+
var ANALYTICS_MCP_TOOL_NAMES = import_agent_qa_mcp.AGENT_QA_TOOL_PROJECTIONS.map(
|
|
384
|
+
(projection) => projection.name
|
|
385
|
+
);
|
|
386
|
+
var BaseProperties = {
|
|
387
|
+
agent_qa_version: AgentQaVersionSchema.optional(),
|
|
388
|
+
surface: import_zod.z.enum(ANALYTICS_EMITTING_HOSTS).optional(),
|
|
389
|
+
runtime_context: import_zod.z.enum(ANALYTICS_RUNTIME_CONTEXTS).optional(),
|
|
390
|
+
is_internal: import_zod.z.literal(true).optional(),
|
|
391
|
+
agent_product: import_zod.z.enum(ANALYTICS_AGENT_PRODUCTS).optional(),
|
|
392
|
+
// PostHog's own directive, a literal `false` and never anything else: it suppresses person-profile
|
|
393
|
+
// creation, which is what keeps an anonymous distinct id anonymous on their side too.
|
|
394
|
+
$process_person_profile: import_zod.z.literal(false).optional()
|
|
395
|
+
};
|
|
396
|
+
function member(name, shape) {
|
|
397
|
+
return import_zod.z.strictObject({
|
|
398
|
+
name: import_zod.z.literal(name),
|
|
399
|
+
properties: import_zod.z.strictObject({ ...BaseProperties, ...shape })
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
var RunCompletionShape = {
|
|
403
|
+
trigger_source: import_zod.z.enum(ANALYTICS_TRIGGER_SOURCES).optional(),
|
|
404
|
+
runner: import_zod.z.enum(ANALYTICS_RUNNERS).optional(),
|
|
405
|
+
platform: import_zod.z.enum(ANALYTICS_PLATFORMS).optional(),
|
|
406
|
+
status: import_zod.z.enum(ANALYTICS_RUN_STATUSES).optional(),
|
|
407
|
+
failure_category: import_zod.z.enum(ANALYTICS_FAILURE_CATEGORIES).optional(),
|
|
408
|
+
duration_ms: NonNegativeNumber.optional(),
|
|
409
|
+
run_id: RunIdSchema.optional(),
|
|
410
|
+
// 2026-08-28 (405-13, D-21/DETL-06). The parent-link property was deleted here. It was declared
|
|
411
|
+
// with no producer anywhere in this package — 402-09 put one run row per run and moved member
|
|
412
|
+
// identity onto handles, and 405-07 then deleted the column it named — so it could only ever
|
|
413
|
+
// have travelled as `undefined`. Same disposition and same reason as the healing counter below.
|
|
414
|
+
step_count: NonNegativeInteger.optional(),
|
|
415
|
+
passed_step_count: NonNegativeInteger.optional(),
|
|
416
|
+
failed_step_count: NonNegativeInteger.optional(),
|
|
417
|
+
skipped_step_count: NonNegativeInteger.optional(),
|
|
418
|
+
retry_count: NonNegativeInteger.optional(),
|
|
419
|
+
input_tokens: NonNegativeInteger.optional(),
|
|
420
|
+
output_tokens: NonNegativeInteger.optional(),
|
|
421
|
+
total_tokens: NonNegativeInteger.optional(),
|
|
422
|
+
is_flaky: import_zod.z.boolean().optional(),
|
|
423
|
+
memory_enabled: import_zod.z.boolean().optional()
|
|
424
|
+
};
|
|
425
|
+
var TestRunCompleted = member("agent-qa.test_run.completed", {
|
|
426
|
+
...RunCompletionShape,
|
|
427
|
+
test_id: TestIdSchema.optional()
|
|
428
|
+
});
|
|
429
|
+
var SuiteRunCompleted = member("agent-qa.suite_run.completed", {
|
|
430
|
+
...RunCompletionShape,
|
|
431
|
+
suite_id: SuiteIdSchema.optional(),
|
|
432
|
+
suite_child_count: NonNegativeInteger.optional(),
|
|
433
|
+
suite_passed_count: NonNegativeInteger.optional(),
|
|
434
|
+
suite_failed_count: NonNegativeInteger.optional(),
|
|
435
|
+
suite_skipped_count: NonNegativeInteger.optional(),
|
|
436
|
+
suite_execution_mode: import_zod.z.enum(ANALYTICS_SUITE_EXECUTION_MODES).optional()
|
|
437
|
+
});
|
|
438
|
+
var ChatSessionCompleted = member("agent-qa.chat.session.completed", {
|
|
439
|
+
duration_ms: NonNegativeNumber.optional(),
|
|
440
|
+
turn_count: NonNegativeInteger.optional(),
|
|
441
|
+
slash_command_count: NonNegativeInteger.optional(),
|
|
442
|
+
function_call_count: NonNegativeInteger.optional(),
|
|
443
|
+
end_reason: import_zod.z.enum(ANALYTICS_CHAT_END_REASONS).optional()
|
|
444
|
+
});
|
|
445
|
+
var ChatTurnCompleted = member("agent-qa.chat.turn.completed", {
|
|
446
|
+
duration_ms: NonNegativeNumber.optional(),
|
|
447
|
+
tool_call_count: NonNegativeInteger.optional(),
|
|
448
|
+
input_tokens: NonNegativeInteger.optional(),
|
|
449
|
+
output_tokens: NonNegativeInteger.optional(),
|
|
450
|
+
total_tokens: NonNegativeInteger.optional(),
|
|
451
|
+
errored: import_zod.z.boolean().optional()
|
|
452
|
+
});
|
|
453
|
+
var DaemonJobCompleted = member("agent-qa.daemon.job.completed", {
|
|
454
|
+
job_kind: import_zod.z.enum(ANALYTICS_DAEMON_JOB_KINDS).optional(),
|
|
455
|
+
outcome: import_zod.z.enum(ANALYTICS_DAEMON_JOB_OUTCOMES).optional(),
|
|
456
|
+
duration_ms: NonNegativeNumber.optional(),
|
|
457
|
+
attempt_count: NonNegativeInteger.optional(),
|
|
458
|
+
run_id: RunIdSchema.optional()
|
|
459
|
+
});
|
|
460
|
+
var McpServerLifecycle = member("agent-qa.mcp.server.lifecycle", {
|
|
461
|
+
mcp_server_state: import_zod.z.enum(ANALYTICS_MCP_SERVER_STATES).optional(),
|
|
462
|
+
mcp_transport: import_zod.z.enum(ANALYTICS_MCP_TRANSPORTS).optional(),
|
|
463
|
+
mcp_tool_count: NonNegativeInteger.optional()
|
|
464
|
+
});
|
|
465
|
+
var McpToolInvoked = member("agent-qa.mcp.tool.invoked", {
|
|
466
|
+
tool_name: import_zod.z.enum(ANALYTICS_MCP_TOOL_NAMES).optional(),
|
|
467
|
+
mcp_tool_status: import_zod.z.enum(ANALYTICS_MCP_TOOL_STATUSES).optional(),
|
|
468
|
+
mcp_error_category: import_zod.z.enum(ANALYTICS_MCP_ERROR_CATEGORIES).optional(),
|
|
469
|
+
mcp_transport: import_zod.z.enum(ANALYTICS_MCP_TRANSPORTS).optional(),
|
|
470
|
+
duration_ms: NonNegativeNumber.optional()
|
|
471
|
+
});
|
|
472
|
+
var InitCompleted = member("agent-qa.init.completed", {
|
|
473
|
+
init_mode: import_zod.z.enum(ANALYTICS_INIT_MODES).optional(),
|
|
474
|
+
outcome: import_zod.z.enum(ANALYTICS_INIT_OUTCOMES).optional(),
|
|
475
|
+
created_file_count: NonNegativeInteger.optional(),
|
|
476
|
+
duration_ms: NonNegativeNumber.optional()
|
|
477
|
+
});
|
|
478
|
+
var ExplainAnswered = member("agent-qa.explain.answered", {
|
|
479
|
+
cited_row_count: NonNegativeInteger.optional(),
|
|
480
|
+
duration_ms: NonNegativeNumber.optional()
|
|
481
|
+
});
|
|
482
|
+
var ExplainAbstained = member("agent-qa.explain.abstained", {
|
|
483
|
+
suggested_row_count: NonNegativeInteger.optional(),
|
|
484
|
+
catalog_empty: import_zod.z.boolean().optional(),
|
|
485
|
+
duration_ms: NonNegativeNumber.optional()
|
|
486
|
+
});
|
|
487
|
+
var ANALYTICS_EVENT_NAMES = [
|
|
488
|
+
"agent-qa.test_run.completed",
|
|
489
|
+
"agent-qa.suite_run.completed",
|
|
490
|
+
"agent-qa.chat.session.completed",
|
|
491
|
+
"agent-qa.chat.turn.completed",
|
|
492
|
+
"agent-qa.daemon.job.completed",
|
|
493
|
+
"agent-qa.mcp.server.lifecycle",
|
|
494
|
+
"agent-qa.mcp.tool.invoked",
|
|
495
|
+
"agent-qa.init.completed",
|
|
496
|
+
"agent-qa.explain.answered",
|
|
497
|
+
"agent-qa.explain.abstained"
|
|
498
|
+
];
|
|
499
|
+
var RETIRED_V33_EVENT_NAMES = [
|
|
500
|
+
"agent-qa.analytics.initialized",
|
|
501
|
+
"agent-qa.analytics.test_event",
|
|
502
|
+
"agent-qa.dashboard.opened",
|
|
503
|
+
"agent-qa.dashboard.live_mode.started",
|
|
504
|
+
"agent-qa.dashboard.entity.created"
|
|
505
|
+
];
|
|
506
|
+
var ANALYTICS_EVENT_SCHEMA_BY_NAME = {
|
|
507
|
+
"agent-qa.test_run.completed": TestRunCompleted,
|
|
508
|
+
"agent-qa.suite_run.completed": SuiteRunCompleted,
|
|
509
|
+
"agent-qa.chat.session.completed": ChatSessionCompleted,
|
|
510
|
+
"agent-qa.chat.turn.completed": ChatTurnCompleted,
|
|
511
|
+
"agent-qa.daemon.job.completed": DaemonJobCompleted,
|
|
512
|
+
"agent-qa.mcp.server.lifecycle": McpServerLifecycle,
|
|
513
|
+
"agent-qa.mcp.tool.invoked": McpToolInvoked,
|
|
514
|
+
"agent-qa.init.completed": InitCompleted,
|
|
515
|
+
"agent-qa.explain.answered": ExplainAnswered,
|
|
516
|
+
"agent-qa.explain.abstained": ExplainAbstained
|
|
517
|
+
};
|
|
518
|
+
var ANALYTICS_EVENT_MEMBERS = [
|
|
519
|
+
TestRunCompleted,
|
|
520
|
+
SuiteRunCompleted,
|
|
521
|
+
ChatSessionCompleted,
|
|
522
|
+
ChatTurnCompleted,
|
|
523
|
+
DaemonJobCompleted,
|
|
524
|
+
McpServerLifecycle,
|
|
525
|
+
McpToolInvoked,
|
|
526
|
+
InitCompleted,
|
|
527
|
+
ExplainAnswered,
|
|
528
|
+
ExplainAbstained
|
|
529
|
+
];
|
|
530
|
+
var AgentQaAnalyticsEventSchema = import_zod.z.discriminatedUnion("name", ANALYTICS_EVENT_MEMBERS);
|
|
531
|
+
function assertNoUnconstrainedStrings(members) {
|
|
532
|
+
const offenders = [];
|
|
533
|
+
for (const memberSchema of members) {
|
|
534
|
+
const name = memberNameOf(memberSchema);
|
|
535
|
+
walk(memberSchema, "", name, offenders);
|
|
536
|
+
}
|
|
537
|
+
if (offenders.length > 0) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`analytics event union carries unconstrained string field(s):
|
|
540
|
+
${offenders.join("\n ")}`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function memberNameOf(schema) {
|
|
545
|
+
if (schema instanceof import_zod.z.ZodObject) {
|
|
546
|
+
const nameSchema = schema.shape.name;
|
|
547
|
+
if (nameSchema instanceof import_zod.z.ZodLiteral) {
|
|
548
|
+
const values = nameSchema.def.values;
|
|
549
|
+
const first = Array.isArray(values) ? values[0] : values;
|
|
550
|
+
if (typeof first === "string") return first;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return "<unnamed member>";
|
|
554
|
+
}
|
|
555
|
+
function walk(schema, path, member2, offenders) {
|
|
556
|
+
const unwrapped = unwrap(schema);
|
|
557
|
+
if (unwrapped instanceof import_zod.z.ZodObject) {
|
|
558
|
+
for (const [key, child] of Object.entries(unwrapped.shape)) {
|
|
559
|
+
walk(child, path === "" ? key : `${path}.${key}`, member2, offenders);
|
|
560
|
+
}
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (unwrapped instanceof import_zod.z.ZodEnum || unwrapped instanceof import_zod.z.ZodLiteral) return;
|
|
564
|
+
if (unwrapped instanceof import_zod.z.ZodNumber || unwrapped instanceof import_zod.z.ZodBoolean || unwrapped instanceof import_zod.z.ZodNull || unwrapped instanceof import_zod.z.ZodUndefined) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (unwrapped instanceof import_zod.z.ZodArray) {
|
|
568
|
+
walk(unwrapped.element, path, member2, offenders);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (unwrapped instanceof import_zod.z.ZodRecord) {
|
|
572
|
+
walk(unwrapped.keyType, path, member2, offenders);
|
|
573
|
+
walk(unwrapped.valueType, path, member2, offenders);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (unwrapped instanceof import_zod.z.ZodUnion) {
|
|
577
|
+
for (const option of unwrapped.options) {
|
|
578
|
+
walk(option, path, member2, offenders);
|
|
579
|
+
}
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (unwrapped instanceof import_zod.z.ZodString) {
|
|
583
|
+
if (!isKnownIdCodec(unwrapped)) {
|
|
584
|
+
offenders.push(`${member2} :: ${path} | unconstrained string`);
|
|
585
|
+
}
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
offenders.push(`${member2} :: ${path} | unrecognised construct, cannot prove it is constrained`);
|
|
589
|
+
}
|
|
590
|
+
function isKnownIdCodec(schema) {
|
|
591
|
+
const meta = schema.meta();
|
|
592
|
+
if (meta?.[ANALYTICS_VOCABULARY_META_KEY] !== ANALYTICS_KNOWN_ID_MARKER) return false;
|
|
593
|
+
return (schema.def.checks ?? []).length > 0;
|
|
594
|
+
}
|
|
595
|
+
function unwrap(schema) {
|
|
596
|
+
let current = schema;
|
|
597
|
+
for (; ; ) {
|
|
598
|
+
if (current instanceof import_zod.z.ZodOptional || current instanceof import_zod.z.ZodNullable || current instanceof import_zod.z.ZodReadonly) {
|
|
599
|
+
current = current.unwrap();
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (current instanceof import_zod.z.ZodDefault) {
|
|
603
|
+
current = current.def.innerType;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
return current;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/identity.ts
|
|
611
|
+
var import_node_crypto = require("crypto");
|
|
612
|
+
var import_promises = require("fs/promises");
|
|
613
|
+
var import_node_os = require("os");
|
|
614
|
+
var import_node_path = require("path");
|
|
615
|
+
var import_id_agent = require("id-agent");
|
|
616
|
+
var ANALYTICS_USER_ID_PREFIX = "u";
|
|
617
|
+
var ANALYTICS_USER_ID_WORDS = 10;
|
|
618
|
+
var LOCAL_ANALYTICS_ID_PATTERN = /^u_([a-z]+-){9}[a-z]+$/;
|
|
619
|
+
function getAnalyticsIdentityPath(env = process.env, homeDir = (0, import_node_os.homedir)()) {
|
|
620
|
+
if (env.XDG_DATA_HOME) return (0, import_node_path.join)(env.XDG_DATA_HOME, "agent-qa", "analytics.json");
|
|
621
|
+
return (0, import_node_path.join)(homeDir, ".agent-qa", "analytics.json");
|
|
622
|
+
}
|
|
623
|
+
function generateAnalyticsUserId() {
|
|
624
|
+
return (0, import_id_agent.idAgent)({ prefix: ANALYTICS_USER_ID_PREFIX, words: ANALYTICS_USER_ID_WORDS });
|
|
625
|
+
}
|
|
626
|
+
function parseAnalyticsIdentityStore(value) {
|
|
627
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
628
|
+
const distinctId = value.distinctId;
|
|
629
|
+
if (typeof distinctId !== "string" || !LOCAL_ANALYTICS_ID_PATTERN.test(distinctId)) return null;
|
|
630
|
+
return { distinctId, is_internal: value.is_internal === true };
|
|
631
|
+
}
|
|
632
|
+
async function readStore(identityPath) {
|
|
633
|
+
let data;
|
|
634
|
+
try {
|
|
635
|
+
data = await (0, import_promises.readFile)(identityPath, "utf-8");
|
|
636
|
+
} catch (error) {
|
|
637
|
+
if (error.code === "ENOENT") return { kind: "missing" };
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
const record = parseAnalyticsIdentityStore(JSON.parse(data));
|
|
642
|
+
return record === null ? { kind: "invalid" } : { kind: "valid", record };
|
|
643
|
+
} catch {
|
|
644
|
+
return { kind: "invalid" };
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async function placeNewRecord(identityPath, replace) {
|
|
648
|
+
await (0, import_promises.mkdir)((0, import_node_path.dirname)(identityPath), { recursive: true });
|
|
649
|
+
const temp = `${identityPath}.${(0, import_node_crypto.randomUUID)()}.tmp`;
|
|
650
|
+
const record = {
|
|
651
|
+
distinctId: generateAnalyticsUserId(),
|
|
652
|
+
is_internal: false
|
|
653
|
+
};
|
|
654
|
+
try {
|
|
655
|
+
await (0, import_promises.writeFile)(temp, JSON.stringify(record, null, 2), { mode: 384, flag: "wx" });
|
|
656
|
+
await (0, import_promises.chmod)(temp, 384);
|
|
657
|
+
if (replace) {
|
|
658
|
+
await (0, import_promises.rename)(temp, identityPath);
|
|
659
|
+
} else {
|
|
660
|
+
await (0, import_promises.link)(temp, identityPath).catch((error) => {
|
|
661
|
+
if (error.code !== "EEXIST") throw error;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
} finally {
|
|
665
|
+
await (0, import_promises.rm)(temp, { force: true });
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function readAnalyticsIdentity(path) {
|
|
669
|
+
try {
|
|
670
|
+
const found = await readStore(path ?? getAnalyticsIdentityPath());
|
|
671
|
+
return found.kind === "valid" ? found.record : null;
|
|
672
|
+
} catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function resolveAnalyticsAgentProduct(env = process.env) {
|
|
677
|
+
if (env.CLAUDECODE !== void 0 || env.CLAUDE_CODE_ENTRYPOINT !== void 0) return "claude_code";
|
|
678
|
+
if (env.CURSOR_AGENT !== void 0) return "cursor";
|
|
679
|
+
if (env.GEMINI_CLI !== void 0) return "gemini_cli";
|
|
680
|
+
if (env.AUGMENT_AGENT !== void 0) return "augment";
|
|
681
|
+
if (env.GOOSE_TERMINAL !== void 0 || env.AGENT === "goose") return "goose";
|
|
682
|
+
if (env.OPENCODE_CLIENT !== void 0) return "opencode";
|
|
683
|
+
if (env.CODEX_SANDBOX !== void 0) return "codex";
|
|
684
|
+
if (env.CLINE_ACTIVE !== void 0) return "cline";
|
|
685
|
+
if (env.AGENT === "amp") return "amp";
|
|
686
|
+
if (typeof env.AGENT === "string" && env.AGENT.length > 0) return "unknown";
|
|
687
|
+
return void 0;
|
|
688
|
+
}
|
|
689
|
+
async function resolveAnalyticsIdentity(options = {}) {
|
|
690
|
+
const env = options.env ?? process.env;
|
|
691
|
+
const agentProduct = resolveAnalyticsAgentProduct(env);
|
|
692
|
+
const attribution = agentProduct === void 0 ? {} : { agentProduct };
|
|
693
|
+
if (options.ci === true) {
|
|
694
|
+
return { distinctId: "ci_" + (0, import_node_crypto.randomUUID)(), runtimeContext: "ci", ...attribution };
|
|
695
|
+
}
|
|
696
|
+
const identityPath = options.identityPath ?? getAnalyticsIdentityPath(env, options.homeDir);
|
|
697
|
+
const found = await readStore(identityPath);
|
|
698
|
+
if (found.kind !== "valid") await placeNewRecord(identityPath, found.kind === "invalid");
|
|
699
|
+
const stored = found.kind === "valid" ? found : await readStore(identityPath);
|
|
700
|
+
if (stored.kind !== "valid") {
|
|
701
|
+
throw new Error("the analytics identity store did not hold a valid record");
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
distinctId: stored.record.distinctId,
|
|
705
|
+
runtimeContext: "user",
|
|
706
|
+
isInternal: stored.record.is_internal,
|
|
707
|
+
...attribution
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/run-subscriber.ts
|
|
712
|
+
function emptyStepTally() {
|
|
713
|
+
return { step_count: 0, passed_step_count: 0, failed_step_count: 0, skipped_step_count: 0 };
|
|
714
|
+
}
|
|
715
|
+
function readString(value) {
|
|
716
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
717
|
+
}
|
|
718
|
+
function readNumber(value) {
|
|
719
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
720
|
+
}
|
|
721
|
+
function readIndex(value) {
|
|
722
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
723
|
+
}
|
|
724
|
+
function normalizeTriggerSource(value) {
|
|
725
|
+
return ANALYTICS_TRIGGER_SOURCES.find((candidate) => candidate === value) ?? "unknown";
|
|
726
|
+
}
|
|
727
|
+
function normalizeRunner(value) {
|
|
728
|
+
return ANALYTICS_RUNNERS.find((candidate) => candidate === value) ?? "unknown";
|
|
729
|
+
}
|
|
730
|
+
function outcomeToAnalyticsStatus(outcome) {
|
|
731
|
+
switch (outcome) {
|
|
732
|
+
case "passed":
|
|
733
|
+
case "healed":
|
|
734
|
+
return "passed";
|
|
735
|
+
case "failed":
|
|
736
|
+
case "timeout":
|
|
737
|
+
return "failed";
|
|
738
|
+
case "skipped":
|
|
739
|
+
return "skipped";
|
|
740
|
+
case "cancelled":
|
|
741
|
+
return "cancelled";
|
|
742
|
+
default:
|
|
743
|
+
return "unknown";
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function definedProperties(properties) {
|
|
747
|
+
const output = {};
|
|
748
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
749
|
+
if (value !== void 0) output[key] = value;
|
|
750
|
+
}
|
|
751
|
+
return output;
|
|
752
|
+
}
|
|
753
|
+
function createAnalyticsRunSubscriber(options) {
|
|
754
|
+
const port = options.port;
|
|
755
|
+
const standard = options.standardProperties ?? {};
|
|
756
|
+
const triggerSource = normalizeTriggerSource(options.triggerSource);
|
|
757
|
+
const runner = normalizeRunner(options.runner);
|
|
758
|
+
let runId;
|
|
759
|
+
let suiteId;
|
|
760
|
+
let finished = false;
|
|
761
|
+
let aggregateVerdict;
|
|
762
|
+
let sawSuite = false;
|
|
763
|
+
const members = /* @__PURE__ */ new Map();
|
|
764
|
+
const liveSteps = /* @__PURE__ */ new Map();
|
|
765
|
+
function stepTallyFor(testIndex) {
|
|
766
|
+
let tally = liveSteps.get(testIndex);
|
|
767
|
+
if (!tally) {
|
|
768
|
+
tally = emptyStepTally();
|
|
769
|
+
liveSteps.set(testIndex, tally);
|
|
770
|
+
}
|
|
771
|
+
return tally;
|
|
772
|
+
}
|
|
773
|
+
function handleTestStarted(payload) {
|
|
774
|
+
const testIndex = readIndex(payload.testIndex);
|
|
775
|
+
if (testIndex === void 0) return;
|
|
776
|
+
const payloadSuiteId = readString(payload.suiteId);
|
|
777
|
+
if (payloadSuiteId) {
|
|
778
|
+
sawSuite = true;
|
|
779
|
+
suiteId = analyticsSuiteId(payloadSuiteId) ?? suiteId;
|
|
780
|
+
}
|
|
781
|
+
liveSteps.set(testIndex, emptyStepTally());
|
|
782
|
+
}
|
|
783
|
+
function handleStepFinalized(payload) {
|
|
784
|
+
const testIndex = readIndex(payload.testIndex);
|
|
785
|
+
if (testIndex === void 0) return;
|
|
786
|
+
const tally = stepTallyFor(testIndex);
|
|
787
|
+
tally.step_count += 1;
|
|
788
|
+
switch (payload.status) {
|
|
789
|
+
case "passed":
|
|
790
|
+
case "healed":
|
|
791
|
+
tally.passed_step_count += 1;
|
|
792
|
+
break;
|
|
793
|
+
case "failed":
|
|
794
|
+
case "cancelled":
|
|
795
|
+
tally.failed_step_count += 1;
|
|
796
|
+
break;
|
|
797
|
+
case "skipped":
|
|
798
|
+
tally.skipped_step_count += 1;
|
|
799
|
+
break;
|
|
800
|
+
default:
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
const usage = payload.tokenUsage;
|
|
804
|
+
if (usage !== void 0) {
|
|
805
|
+
const tokens = tally.tokens ?? { input: 0, output: 0, total: 0 };
|
|
806
|
+
tokens.input += usage.promptTokens;
|
|
807
|
+
tokens.output += usage.completionTokens;
|
|
808
|
+
tokens.total += usage.totalTokens;
|
|
809
|
+
tally.tokens = tokens;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function handleTestFinished(payload) {
|
|
813
|
+
const testIndex = readIndex(payload.testIndex);
|
|
814
|
+
if (testIndex === void 0) return;
|
|
815
|
+
const steps = liveSteps.get(testIndex) ?? emptyStepTally();
|
|
816
|
+
members.set(testIndex, {
|
|
817
|
+
testId: readString(payload.testId),
|
|
818
|
+
outcome: readString(payload.outcome) ?? "unknown",
|
|
819
|
+
durationMs: readNumber(payload.durationMs),
|
|
820
|
+
retryCount: readNumber(payload.retryCount),
|
|
821
|
+
steps
|
|
822
|
+
});
|
|
823
|
+
liveSteps.delete(testIndex);
|
|
824
|
+
}
|
|
825
|
+
function captureTestRun(record) {
|
|
826
|
+
const status = outcomeToAnalyticsStatus(record.outcome);
|
|
827
|
+
const testId = analyticsTestId(record.testId);
|
|
828
|
+
const { tokens: _tokens, ...steps } = record.steps;
|
|
829
|
+
const properties = definedProperties({
|
|
830
|
+
...standard,
|
|
831
|
+
run_id: runId,
|
|
832
|
+
test_id: testId,
|
|
833
|
+
trigger_source: triggerSource,
|
|
834
|
+
runner,
|
|
835
|
+
status,
|
|
836
|
+
duration_ms: record.durationMs,
|
|
837
|
+
retry_count: record.retryCount,
|
|
838
|
+
is_flaky: record.retryCount !== void 0 ? status === "passed" && record.retryCount > 0 : void 0,
|
|
839
|
+
...steps,
|
|
840
|
+
// 405 D-07. Spread in only when the member measured at least one step. `definedProperties`
|
|
841
|
+
// would drop three `undefined`s anyway, but building them unconditionally would mean writing
|
|
842
|
+
// `input_tokens: tokens?.input ?? 0` somewhere — and that `?? 0` is exactly the fabricated
|
|
843
|
+
// measurement this whole path refuses to make.
|
|
844
|
+
...record.steps.tokens === void 0 ? {} : {
|
|
845
|
+
input_tokens: record.steps.tokens.input,
|
|
846
|
+
output_tokens: record.steps.tokens.output,
|
|
847
|
+
total_tokens: record.steps.tokens.total
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
port.capture({ name: "agent-qa.test_run.completed", properties });
|
|
851
|
+
}
|
|
852
|
+
function captureSuiteRun(records) {
|
|
853
|
+
const statuses = records.map((record) => outcomeToAnalyticsStatus(record.outcome));
|
|
854
|
+
const properties = definedProperties({
|
|
855
|
+
...standard,
|
|
856
|
+
run_id: runId,
|
|
857
|
+
suite_id: suiteId,
|
|
858
|
+
trigger_source: triggerSource,
|
|
859
|
+
runner,
|
|
860
|
+
status: outcomeToAnalyticsStatus(aggregateVerdict),
|
|
861
|
+
duration_ms: records.reduce((sum, record) => sum + (record.durationMs ?? 0), 0),
|
|
862
|
+
suite_child_count: records.length,
|
|
863
|
+
suite_passed_count: statuses.filter((status) => status === "passed").length,
|
|
864
|
+
suite_failed_count: statuses.filter((status) => status === "failed").length,
|
|
865
|
+
suite_skipped_count: statuses.filter((status) => status === "skipped").length
|
|
866
|
+
});
|
|
867
|
+
port.capture({ name: "agent-qa.suite_run.completed", properties });
|
|
868
|
+
}
|
|
869
|
+
return {
|
|
870
|
+
handler(envelope) {
|
|
871
|
+
try {
|
|
872
|
+
runId = analyticsRunId(envelope.runId);
|
|
873
|
+
switch (envelope.type) {
|
|
874
|
+
case "testStarted":
|
|
875
|
+
handleTestStarted(envelope.payload);
|
|
876
|
+
break;
|
|
877
|
+
case "stepFinalized":
|
|
878
|
+
handleStepFinalized(envelope.payload);
|
|
879
|
+
break;
|
|
880
|
+
case "testRetryScheduled": {
|
|
881
|
+
const retryIndex = readIndex(envelope.payload.testIndex);
|
|
882
|
+
if (retryIndex !== void 0) liveSteps.delete(retryIndex);
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
case "testFinished":
|
|
886
|
+
handleTestFinished(envelope.payload);
|
|
887
|
+
break;
|
|
888
|
+
case "verdict":
|
|
889
|
+
aggregateVerdict = readString(envelope.payload.verdict) ?? aggregateVerdict;
|
|
890
|
+
break;
|
|
891
|
+
case "runFinished":
|
|
892
|
+
aggregateVerdict = readString(envelope.payload.aggregateVerdict) ?? aggregateVerdict;
|
|
893
|
+
finished = true;
|
|
894
|
+
break;
|
|
895
|
+
default:
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
} catch {
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
async flush() {
|
|
902
|
+
if (!finished) {
|
|
903
|
+
await port.flush();
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
try {
|
|
907
|
+
const records = [...members.values()];
|
|
908
|
+
for (const record of records) captureTestRun(record);
|
|
909
|
+
if (sawSuite) captureSuiteRun(records);
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
912
|
+
await port.flush();
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
917
|
+
0 && (module.exports = {
|
|
918
|
+
ANALYTICS_AGENT_PRODUCTS,
|
|
919
|
+
ANALYTICS_CHAT_END_REASONS,
|
|
920
|
+
ANALYTICS_DAEMON_JOB_KINDS,
|
|
921
|
+
ANALYTICS_DAEMON_JOB_OUTCOMES,
|
|
922
|
+
ANALYTICS_EMITTING_HOSTS,
|
|
923
|
+
ANALYTICS_EVENT_MEMBERS,
|
|
924
|
+
ANALYTICS_EVENT_NAMES,
|
|
925
|
+
ANALYTICS_EVENT_SCHEMA_BY_NAME,
|
|
926
|
+
ANALYTICS_FAILURE_CATEGORIES,
|
|
927
|
+
ANALYTICS_INIT_MODES,
|
|
928
|
+
ANALYTICS_INIT_OUTCOMES,
|
|
929
|
+
ANALYTICS_KNOWN_ID_MARKER,
|
|
930
|
+
ANALYTICS_MCP_ERROR_CATEGORIES,
|
|
931
|
+
ANALYTICS_MCP_SERVER_STATES,
|
|
932
|
+
ANALYTICS_MCP_TOOL_NAMES,
|
|
933
|
+
ANALYTICS_MCP_TOOL_STATUSES,
|
|
934
|
+
ANALYTICS_MCP_TRANSPORTS,
|
|
935
|
+
ANALYTICS_PLATFORMS,
|
|
936
|
+
ANALYTICS_RUNNERS,
|
|
937
|
+
ANALYTICS_RUNTIME_CONTEXTS,
|
|
938
|
+
ANALYTICS_RUN_STATUSES,
|
|
939
|
+
ANALYTICS_SUITE_EXECUTION_MODES,
|
|
940
|
+
ANALYTICS_TRIGGER_SOURCES,
|
|
941
|
+
ANALYTICS_USER_ID_PREFIX,
|
|
942
|
+
ANALYTICS_USER_ID_WORDS,
|
|
943
|
+
ANALYTICS_VOCABULARY_META_KEY,
|
|
944
|
+
AgentQaAnalyticsEventSchema,
|
|
945
|
+
RETIRED_V33_EVENT_NAMES,
|
|
946
|
+
analyticsRunId,
|
|
947
|
+
analyticsSuiteId,
|
|
948
|
+
analyticsTestId,
|
|
949
|
+
assertNoUnconstrainedStrings,
|
|
950
|
+
composeAnalytics,
|
|
951
|
+
createAnalyticsPort,
|
|
952
|
+
createAnalyticsRunSubscriber,
|
|
953
|
+
createPosthogTransport,
|
|
954
|
+
generateAnalyticsUserId,
|
|
955
|
+
getAnalyticsIdentityPath,
|
|
956
|
+
maskEventForSend,
|
|
957
|
+
readAnalyticsIdentity,
|
|
958
|
+
resolveAnalyticsAgentProduct,
|
|
959
|
+
resolveAnalyticsIdentity
|
|
960
|
+
});
|
|
961
|
+
//# sourceMappingURL=index.cjs.map
|