@dailephd/my-dev-kit-lab 0.9.2 → 0.9.4
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/CHANGELOG.md +17 -1
- package/README.md +9 -5
- package/dist/src/agents/adapters/codexAdapter.js +4 -1
- package/dist/src/cli/help.js +38 -2
- package/dist/src/cli/runLabCli.js +9 -1
- package/dist/src/commands/buildGalleryCommand.js +16 -3
- package/dist/src/commands/runComparePromptsCommand.js +109 -0
- package/dist/src/commands/runExperimentRunCommand.js +47 -2
- package/dist/src/core/processTree.js +41 -17
- package/dist/src/core/runMeasuredCommand.js +14 -8
- package/dist/src/evaluation/affectedNeighborhood.js +140 -32
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +23 -1
- package/dist/src/experiments/plugins/agentSuccessRate/campaignExecution.js +7 -2
- package/dist/src/experiments/plugins/agentSuccessRate/campaignIdentity.js +29 -4
- package/dist/src/experiments/plugins/agentSuccessRate/campaignResumableExecution.js +1 -1
- package/dist/src/experiments/plugins/agentSuccessRate/config.js +47 -2
- package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +4 -2
- package/dist/src/experiments/plugins/agentSuccessRate/promptContract.js +121 -0
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +32 -2
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +105 -11
- package/dist/src/experiments/plugins/agentSuccessRate/strictDiffResponse.js +312 -0
- package/dist/src/gallery/galleryPathSafety.js +111 -0
- package/dist/src/gallery/readGallerySources.js +542 -0
- package/dist/src/gallery/writeGalleryManifest.js +57 -3
- package/dist/src/report/experiments/buildPluginExperimentReport.js +4 -16
- package/dist/src/report/experiments/htmlSafety.js +32 -0
- package/dist/src/report/experiments/metricEvidence.js +389 -0
- package/dist/src/report/experiments/metricGlossary.js +227 -0
- package/dist/src/report/experiments/metricGlossaryCatalog.js +199 -0
- package/dist/src/report/experiments/promptTemplateComparisonEvidence.js +377 -0
- package/dist/src/report/experiments/promptTemplateComparisonModel.js +436 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +158 -64
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +22 -677
- package/dist/src/report/experiments/renderPromptTemplateComparison.js +192 -0
- package/dist/src/report/experiments/renderSpecializedTextSections.js +633 -0
- package/dist/src/report/experiments/reportSectionRegistry.js +79 -0
- package/dist/src/report/experiments/specializedReportSections.js +142 -0
- package/dist/src/report/experiments/writePluginExperimentReports.js +4 -2
- package/dist/src/securityValidation/attackScenarios/scenarios/secretLeakageScenario.js +146 -133
- package/dist/src/securityValidation/attackScenarios/scenarios/subprocessInjectionScenario.js +54 -36
- package/dist/src/securityValidation/commandRunner.js +13 -6
- package/dist/src/securityValidation/packageChecks/parseNpmPackDryRun.js +46 -21
- package/docs/METRICS.md +11 -1
- package/package.json +3 -3
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readdir, realpath, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { GALLERY_MEDIA_EXTENSIONS, safeGalleryHref } from "./galleryPathSafety.js";
|
|
5
|
+
import { readGallerySources } from "./readGallerySources.js";
|
|
4
6
|
function toRelativePath(outDir, targetPath) {
|
|
5
7
|
if (!targetPath) {
|
|
6
8
|
return undefined;
|
|
@@ -154,6 +156,10 @@ export async function writeExperimentGalleryManifest(options) {
|
|
|
154
156
|
warnings: []
|
|
155
157
|
});
|
|
156
158
|
}
|
|
159
|
+
if (options.sources && options.sources.length > 0) {
|
|
160
|
+
const sourceResult = await readGallerySources({ sources: options.sources, realOutDir: await realpath(outDir) });
|
|
161
|
+
items.push(...sourceResult.items);
|
|
162
|
+
}
|
|
157
163
|
items.push({
|
|
158
164
|
id: "final-demo",
|
|
159
165
|
title: "Final demo artifact index",
|
|
@@ -203,11 +209,59 @@ async function listFiles(dir) {
|
|
|
203
209
|
export function renderGalleryIndex(manifest) {
|
|
204
210
|
return `<!DOCTYPE html>
|
|
205
211
|
<html lang="en">
|
|
206
|
-
<head><meta charset="utf-8" /><title>${escapeHtml(manifest.title)}</title><style>body{font-family:Arial,Helvetica,sans-serif;margin:32px;color:#17212b}section{border:1px solid #d9e1ea;border-radius:8px;padding:16px;margin:12px 0}code{background:#f2f4f7;padding:2px 4px}</style></head>
|
|
212
|
+
<head><meta charset="utf-8" /><title>${escapeHtml(manifest.title)}</title><style>body{font-family:Arial,Helvetica,sans-serif;margin:32px;color:#17212b}section{border:1px solid #d9e1ea;border-radius:8px;padding:16px;margin:12px 0}code{background:#f2f4f7;padding:2px 4px}video,img{max-width:100%;height:auto}a:focus-visible{outline:3px solid #1a5fb4}.unavailable{color:#8a1f1f}</style></head>
|
|
207
213
|
<body><h1>${escapeHtml(manifest.title)}</h1><p>${escapeHtml(manifest.description)}</p>${manifest.items
|
|
208
|
-
.map((item) => `<section><h2>${escapeHtml(item.title)}</h2><p>${escapeHtml(item.description)}</p><p>Status: <strong>${escapeHtml(item.status)}</strong></p><p>Kind: ${escapeHtml(item.kind)}</p>${item.htmlPath ? `<p>HTML: <code>${escapeHtml(item.htmlPath)}</code></p>` : ""}${item.summaryPath ? `<p>Summary: <code>${escapeHtml(item.summaryPath)}</code></p>` : ""}${item.screenshotPath ? `<p>Screenshot: <code>${escapeHtml(item.screenshotPath)}</code></p>` : ""}${item.artifactPaths?.length ? `<ul>${item.artifactPaths.map((artifact) => `<li><code>${escapeHtml(artifact)}</code></li>`).join("")}</ul>` : ""}</section>`)
|
|
214
|
+
.map((item) => `<section><h2>${escapeHtml(item.title)}</h2><p>${escapeHtml(item.description)}</p><p>Status: <strong>${escapeHtml(item.status)}</strong></p><p>Kind: ${escapeHtml(item.kind)}</p>${item.htmlPath ? `<p>HTML: <code>${escapeHtml(item.htmlPath)}</code></p>` : ""}${item.summaryPath ? `<p>Summary: <code>${escapeHtml(item.summaryPath)}</code></p>` : ""}${item.screenshotPath ? `<p>Screenshot: <code>${escapeHtml(item.screenshotPath)}</code></p>` : ""}${item.artifactPaths?.length ? `<ul>${item.artifactPaths.map((artifact) => `<li><code>${escapeHtml(artifact)}</code></li>`).join("")}</ul>` : ""}${renderSourceItemExtras(item)}</section>`)
|
|
209
215
|
.join("")}</body></html>`;
|
|
210
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Additive presentation for items produced by `gallery build --source`. Every href/src is validated by
|
|
219
|
+
* safeGalleryHref independently of text escaping; a value that fails validation is shown as unavailable text,
|
|
220
|
+
* never as a link or media element.
|
|
221
|
+
*/
|
|
222
|
+
function renderSourceItemExtras(item) {
|
|
223
|
+
if (!item.sourceFamily)
|
|
224
|
+
return "";
|
|
225
|
+
const parts = [];
|
|
226
|
+
parts.push(`<p>Source family: <code>${escapeHtml(item.sourceFamily)}</code>${item.sourceId ? ` · Identity: <code>${escapeHtml(item.sourceId)}</code>` : ""}</p>`);
|
|
227
|
+
const metricLine = item.metrics
|
|
228
|
+
.map((metric) => `${escapeHtml(metric.label)}: <strong>${escapeHtml(metric.value)}</strong>${metric.unit ? ` ${escapeHtml(metric.unit)}` : ""}`)
|
|
229
|
+
.join(" · ");
|
|
230
|
+
if (metricLine)
|
|
231
|
+
parts.push(`<p>${metricLine}</p>`);
|
|
232
|
+
const link = (value, label, extensions) => {
|
|
233
|
+
if (!value)
|
|
234
|
+
return "";
|
|
235
|
+
const href = safeGalleryHref(value, extensions);
|
|
236
|
+
return href === null
|
|
237
|
+
? `<li class="unavailable">${escapeHtml(label)} unavailable: unsafe or unsupported path</li>`
|
|
238
|
+
: `<li><a href="${escapeHtml(href)}">${escapeHtml(label)}</a></li>`;
|
|
239
|
+
};
|
|
240
|
+
const links = [
|
|
241
|
+
link(item.htmlPath || undefined, "Open HTML report", [".html"]),
|
|
242
|
+
link(item.summaryPath, "Open summary JSON", [".json"]),
|
|
243
|
+
link(item.markdownPath, "Open generated Markdown", GALLERY_MEDIA_EXTENSIONS.markdown),
|
|
244
|
+
...(item.artifactPaths ?? []).map((artifact) => link(artifact, `Artifact ${artifact}`, [...GALLERY_MEDIA_EXTENSIONS.text, ...GALLERY_MEDIA_EXTENSIONS.json]))
|
|
245
|
+
].join("");
|
|
246
|
+
if (links)
|
|
247
|
+
parts.push(`<ul>${links}</ul>`);
|
|
248
|
+
if (item.videoPath) {
|
|
249
|
+
const src = safeGalleryHref(item.videoPath, GALLERY_MEDIA_EXTENSIONS.video);
|
|
250
|
+
parts.push(src === null
|
|
251
|
+
? `<p class="unavailable">Video unavailable: unsafe or unsupported path</p>`
|
|
252
|
+
: `<video controls preload="metadata" src="${escapeHtml(src)}">Your browser does not support embedded video. <a href="${escapeHtml(src)}">Open the WebM video</a>.</video>`);
|
|
253
|
+
}
|
|
254
|
+
for (const [index, screenshot] of (item.screenshotPaths ?? []).entries()) {
|
|
255
|
+
const src = safeGalleryHref(screenshot, GALLERY_MEDIA_EXTENSIONS.screenshot);
|
|
256
|
+
parts.push(src === null
|
|
257
|
+
? `<p class="unavailable">Screenshot ${index + 1} unavailable: unsafe or unsupported path</p>`
|
|
258
|
+
: `<figure><img src="${escapeHtml(src)}" alt="${escapeHtml(item.title)} screenshot ${index + 1}" loading="lazy" /></figure>`);
|
|
259
|
+
}
|
|
260
|
+
if (item.warnings.length > 0) {
|
|
261
|
+
parts.push(`<div><strong>Warnings</strong><ul>${item.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("")}</ul></div>`);
|
|
262
|
+
}
|
|
263
|
+
return parts.join("");
|
|
264
|
+
}
|
|
211
265
|
function escapeHtml(value) {
|
|
212
266
|
return String(value ?? "")
|
|
213
267
|
.replaceAll("&", "&")
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { buildIncrementalChangeStalenessPluginReport } from "./buildIncrementalChangeStalenessPluginReport.js";
|
|
5
|
-
import { buildContextWindowScalingReport } from "./buildContextWindowScalingReport.js";
|
|
6
|
-
import { buildRetrievalPrecisionRecallReport } from "./buildRetrievalPrecisionRecallReport.js";
|
|
7
|
-
import { buildRetrievalQueryStrategyComparisonReport } from "./buildRetrievalQueryStrategyComparisonReport.js";
|
|
8
|
-
import { buildContextPackGenerationReport } from "./buildContextPackGenerationReport.js";
|
|
9
|
-
import { AGENT_SUCCESS_RATE_DETERMINISTIC_STATEMENT, AGENT_SUCCESS_RATE_REAL_AGENT_STATEMENT, buildAgentSuccessRateReport } from "./buildAgentSuccessRateReport.js";
|
|
2
|
+
import { AGENT_SUCCESS_RATE_DETERMINISTIC_STATEMENT, AGENT_SUCCESS_RATE_REAL_AGENT_STATEMENT } from "./buildAgentSuccessRateReport.js";
|
|
3
|
+
import { buildSpecializedReportProjections } from "./specializedReportSections.js";
|
|
10
4
|
// Bulk context-window-scaling evidence is presented by the typed report section; the complete
|
|
11
5
|
// evidence stays in context-window-scaling-execution.json.
|
|
12
6
|
const CONTEXT_WINDOW_SCALING_BULK_KEYS = ["executionEvidence", "aggregate"];
|
|
@@ -26,14 +20,8 @@ const V043_BULK_ARRAY_KEYS = [
|
|
|
26
20
|
export function buildPluginExperimentReport(args) {
|
|
27
21
|
const outputRoot = args.outputRoot ?? readString(args.run.metadata?.outputRoot) ?? null;
|
|
28
22
|
const allOutcomes = args.run.cases.flatMap((experimentCase) => experimentCase.outcomes);
|
|
29
|
-
|
|
30
|
-
const warmIndexReuse =
|
|
31
|
-
const incrementalChangeStaleness = buildIncrementalChangeStalenessPluginReport(args.run);
|
|
32
|
-
const contextWindowScaling = buildContextWindowScalingReport(args.run);
|
|
33
|
-
const retrievalPrecisionRecall = buildRetrievalPrecisionRecallReport(args.run);
|
|
34
|
-
const retrievalQueryStrategyComparison = buildRetrievalQueryStrategyComparisonReport(args.run);
|
|
35
|
-
const contextPackGeneration = buildContextPackGenerationReport(args.run, args.contextPacks);
|
|
36
|
-
const agentSuccessRate = buildAgentSuccessRateReport(args.run);
|
|
23
|
+
// One shared registry owns specialized-family projection selection; each family builder runs once.
|
|
24
|
+
const { contextStrategyComparisonV043, warmIndexReuse, incrementalChangeStaleness, contextWindowScaling, retrievalPrecisionRecall, retrievalQueryStrategyComparison, contextPackGeneration, agentSuccessRate, } = buildSpecializedReportProjections({ run: args.run, contextPacks: args.contextPacks });
|
|
37
25
|
const rawRun = { ...args.run, artifacts: relativizeArtifacts(args.run.artifacts, outputRoot) };
|
|
38
26
|
for (const key of V043_BULK_ARRAY_KEYS) {
|
|
39
27
|
delete rawRun[key];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Context-correct HTML helpers for the generic static report. Text escaping and link validation are separate
|
|
2
|
+
// concerns: escaping alone never makes a URL safe, so every emitted href is validated here independently.
|
|
3
|
+
export function escapeHtml(value) {
|
|
4
|
+
return String(value ?? "")
|
|
5
|
+
.replaceAll("&", "&")
|
|
6
|
+
.replaceAll("<", "<")
|
|
7
|
+
.replaceAll(">", ">")
|
|
8
|
+
.replaceAll('"', """)
|
|
9
|
+
.replaceAll("'", "'");
|
|
10
|
+
}
|
|
11
|
+
const SAFE_ANCHOR_ID = /^[a-z][a-z0-9-]{0,127}$/;
|
|
12
|
+
/** True only for a plain lowercase in-document anchor id that this renderer generated. */
|
|
13
|
+
export function isSafeAnchorId(value) {
|
|
14
|
+
return typeof value === "string" && SAFE_ANCHOR_ID.test(value);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns an href that is safe to emit, or null. Only in-document fragment links to a safe anchor id are
|
|
18
|
+
* permitted: no scheme-bearing URL (javascript:, data:, file:, http:, ...) and no filesystem path is ever linked.
|
|
19
|
+
*/
|
|
20
|
+
export function safeInternalHref(value) {
|
|
21
|
+
if (typeof value !== "string" || !value.startsWith("#"))
|
|
22
|
+
return null;
|
|
23
|
+
return isSafeAnchorId(value.slice(1)) ? value : null;
|
|
24
|
+
}
|
|
25
|
+
/** Derives a safe, lowercase section anchor id from a registry/descriptor identifier. */
|
|
26
|
+
export function sectionAnchorId(identifier) {
|
|
27
|
+
const slug = identifier
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
30
|
+
.replace(/^-+|-+$/g, "");
|
|
31
|
+
return `section-${slug || "unnamed"}`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
// Report-presentation projection of metric evidence. It reads an already built PluginExperimentReport and
|
|
2
|
+
// produces display views and deterministic caveats. It never recalculates a scientific value, never converts
|
|
3
|
+
// unavailable or not-applicable evidence to zero, and never mutates its input. Provenance and reliability are
|
|
4
|
+
// taken only from evidence the report carries; anything else stays explicitly `unknown`.
|
|
5
|
+
import { RAW_FULL_FILE_VARIANT_ID, WARM_INDEX_REUSE_VARIANT_ID } from "../../experiments/plugins/warmIndexReuse/plugin.js";
|
|
6
|
+
import { attemptEvidenceOf } from "../../experiments/plugins/agentSuccessRate/attemptEvidence.js";
|
|
7
|
+
import { pluginLocalMetricId, resolveMetricDescriptor, } from "./metricGlossary.js";
|
|
8
|
+
const KIND_ORDER = [
|
|
9
|
+
"metric-unavailable",
|
|
10
|
+
"metric-not-applicable",
|
|
11
|
+
"telemetry-partial",
|
|
12
|
+
"telemetry-provenance-unknown",
|
|
13
|
+
"telemetry-reliability-low",
|
|
14
|
+
"telemetry-reliability-unknown",
|
|
15
|
+
"estimated-not-provider-usage",
|
|
16
|
+
"glossary-missing",
|
|
17
|
+
];
|
|
18
|
+
/** Collects per-attempt telemetry already recorded in agent-success execution evidence. Pure; no I/O. */
|
|
19
|
+
export function collectMetricReportEvidence(run) {
|
|
20
|
+
// Only the agent-success-rate plugin records per-attempt token telemetry in this shape.
|
|
21
|
+
if (run.pluginId !== "agent-success-rate")
|
|
22
|
+
return {};
|
|
23
|
+
const cases = run.caseExecutionEvidence;
|
|
24
|
+
if (!Array.isArray(cases))
|
|
25
|
+
return {};
|
|
26
|
+
const attempts = [];
|
|
27
|
+
for (const entry of cases) {
|
|
28
|
+
if (!entry || !Array.isArray(entry.treatments))
|
|
29
|
+
continue;
|
|
30
|
+
for (const treatment of entry.treatments) {
|
|
31
|
+
attemptEvidenceOf(treatment).forEach((evidence, index) => {
|
|
32
|
+
const usage = evidence.agentTokenUsage;
|
|
33
|
+
attempts.push({
|
|
34
|
+
caseId: entry.caseId,
|
|
35
|
+
treatmentId: treatment.treatmentId,
|
|
36
|
+
attemptNumber: index + 1,
|
|
37
|
+
providerInvoked: evidence.realAgent?.providerInvoked === true,
|
|
38
|
+
tokenSource: usage ? usage.source : null,
|
|
39
|
+
tokenReliability: usage ? usage.reliability : null,
|
|
40
|
+
totalTokensAvailable: usage !== null && typeof usage.totalTokens === "number"
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { agentSuccessAttempts: attempts };
|
|
46
|
+
}
|
|
47
|
+
const MAX_LISTED = 10;
|
|
48
|
+
const MAX_REASONS = 3;
|
|
49
|
+
const AGENT_SUCCESS_PREFIX = "agent-success-rate.";
|
|
50
|
+
const DESCRIPTION_PATTERN = /^(available|unavailable|not-applicable)(?::\s*([\s\S]*))?$/;
|
|
51
|
+
function provenanceOfTokenSource(source) {
|
|
52
|
+
switch (source) {
|
|
53
|
+
case "provider-reported":
|
|
54
|
+
return "provider-reported";
|
|
55
|
+
case "agent-reported":
|
|
56
|
+
case "cli-json":
|
|
57
|
+
return "agent-reported";
|
|
58
|
+
case "estimated-from-text":
|
|
59
|
+
return "estimated";
|
|
60
|
+
default:
|
|
61
|
+
// "otel" is unattributed telemetry and "unavailable" carries no source: neither is assumed.
|
|
62
|
+
return "unknown";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function reliabilityOf(value) {
|
|
66
|
+
return value === "high" || value === "medium" || value === "low" || value === "unavailable" ? value : "unknown";
|
|
67
|
+
}
|
|
68
|
+
function basisProvenance(descriptor) {
|
|
69
|
+
switch (descriptor?.measurementBasis) {
|
|
70
|
+
case "measured":
|
|
71
|
+
return "measured";
|
|
72
|
+
case "estimated":
|
|
73
|
+
return "estimated";
|
|
74
|
+
case "derived":
|
|
75
|
+
case "verification-verdict":
|
|
76
|
+
case "answer-key-set-measure":
|
|
77
|
+
return "derived";
|
|
78
|
+
default:
|
|
79
|
+
return "unknown";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function isTokenTelemetry(descriptor) {
|
|
83
|
+
return descriptor?.measurementBasis === "agent-token-telemetry";
|
|
84
|
+
}
|
|
85
|
+
function telemetryKey(variantId, caseId) {
|
|
86
|
+
return `${variantId ?? ""}\u0000${caseId ?? ""}`;
|
|
87
|
+
}
|
|
88
|
+
/** Per variant/case token telemetry that the warm-index report already carries. */
|
|
89
|
+
function warmIndexTelemetry(report) {
|
|
90
|
+
const result = new Map();
|
|
91
|
+
for (const project of report.warmIndexReuse?.projects ?? []) {
|
|
92
|
+
for (const task of project.tasks) {
|
|
93
|
+
for (const [variantId, agent] of [
|
|
94
|
+
[RAW_FULL_FILE_VARIANT_ID, task.rawAgent],
|
|
95
|
+
[WARM_INDEX_REUSE_VARIANT_ID, task.warmAgent],
|
|
96
|
+
]) {
|
|
97
|
+
if (!agent)
|
|
98
|
+
continue;
|
|
99
|
+
result.set(telemetryKey(variantId, task.caseId), {
|
|
100
|
+
provenance: provenanceOfTokenSource(agent.tokenUsageSource),
|
|
101
|
+
reliability: reliabilityOf(agent.tokenUsageReliability),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
function commonValue(values, unknownValue) {
|
|
109
|
+
if (values.length === 0)
|
|
110
|
+
return unknownValue;
|
|
111
|
+
const first = values[0];
|
|
112
|
+
return values.every((value) => value === first) ? first : unknownValue;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Aggregate telemetry across contributing measurements. Provenance and reliability are attributed only when every
|
|
116
|
+
* contributor is known and identical; any missing or differing contributor leaves the aggregate unknown.
|
|
117
|
+
*/
|
|
118
|
+
function aggregateTelemetry(parts) {
|
|
119
|
+
if (parts.length === 0 || parts.some((part) => part === null))
|
|
120
|
+
return { provenance: "unknown", reliability: "unknown" };
|
|
121
|
+
const known = parts;
|
|
122
|
+
const provenance = commonValue(known.map((part) => part.provenance), "unknown");
|
|
123
|
+
const reliability = commonValue(known.map((part) => part.reliability), "unknown");
|
|
124
|
+
return { provenance, reliability };
|
|
125
|
+
}
|
|
126
|
+
/** Cumulative warm-index token telemetry per variant/case: the strict prefix of tasks 1..N in the project. */
|
|
127
|
+
function warmIndexCumulativeTelemetry(report) {
|
|
128
|
+
const result = new Map();
|
|
129
|
+
for (const project of report.warmIndexReuse?.projects ?? []) {
|
|
130
|
+
const tasks = [...project.tasks].sort((a, b) => a.taskOrdinal - b.taskOrdinal);
|
|
131
|
+
for (const [variantId, pick] of [
|
|
132
|
+
[RAW_FULL_FILE_VARIANT_ID, (task) => task.rawAgent],
|
|
133
|
+
[WARM_INDEX_REUSE_VARIANT_ID, (task) => task.warmAgent]
|
|
134
|
+
]) {
|
|
135
|
+
const parts = [];
|
|
136
|
+
for (const task of tasks) {
|
|
137
|
+
const agent = pick(task);
|
|
138
|
+
parts.push(agent ? { provenance: provenanceOfTokenSource(agent.tokenUsageSource), reliability: reliabilityOf(agent.tokenUsageReliability) } : null);
|
|
139
|
+
result.set(telemetryKey(variantId, task.caseId), aggregateTelemetry(parts));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
function agentSuccessTelemetry(localId, caseId, variantId, evidence) {
|
|
146
|
+
if (!evidence?.agentSuccessAttempts || caseId === null || variantId === null)
|
|
147
|
+
return null;
|
|
148
|
+
const attempts = evidence.agentSuccessAttempts
|
|
149
|
+
.filter((attempt) => attempt.caseId === caseId && attempt.treatmentId === variantId)
|
|
150
|
+
.sort((a, b) => a.attemptNumber - b.attemptNumber);
|
|
151
|
+
if (attempts.length === 0)
|
|
152
|
+
return null;
|
|
153
|
+
const one = (attempt) => attempt && attempt.providerInvoked && attempt.totalTokensAvailable && attempt.tokenSource !== null
|
|
154
|
+
? { provenance: provenanceOfTokenSource(attempt.tokenSource), reliability: reliabilityOf(attempt.tokenReliability) }
|
|
155
|
+
: null;
|
|
156
|
+
switch (localId) {
|
|
157
|
+
case "firstAttemptProviderTokens":
|
|
158
|
+
return one(attempts[0]);
|
|
159
|
+
case "agentTotalTokens":
|
|
160
|
+
case "finalAttemptProviderTokens":
|
|
161
|
+
return one(attempts[attempts.length - 1]);
|
|
162
|
+
case "totalProviderTokens":
|
|
163
|
+
// Cumulative over the attempts that actually ran provider processes.
|
|
164
|
+
return aggregateTelemetry(attempts.filter((attempt) => attempt.providerInvoked).map(one));
|
|
165
|
+
default:
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function warmUnavailableAgentTokenViews(report, telemetry) {
|
|
170
|
+
const pluginId = report.plugin.id;
|
|
171
|
+
const descriptor = resolveMetricDescriptor(pluginId, "agent-total-tokens");
|
|
172
|
+
const views = [];
|
|
173
|
+
for (const project of report.warmIndexReuse?.projects ?? []) {
|
|
174
|
+
for (const task of project.tasks) {
|
|
175
|
+
for (const [variantId, metric] of [
|
|
176
|
+
[RAW_FULL_FILE_VARIANT_ID, task.raw?.agentTotalTokens],
|
|
177
|
+
[WARM_INDEX_REUSE_VARIANT_ID, task.warm?.agentTotalTokens],
|
|
178
|
+
]) {
|
|
179
|
+
const measurement = metric;
|
|
180
|
+
if (!measurement || measurement.availability === "available")
|
|
181
|
+
continue;
|
|
182
|
+
const known = telemetry.get(telemetryKey(variantId, task.caseId));
|
|
183
|
+
views.push({
|
|
184
|
+
metricId: "agent-total-tokens",
|
|
185
|
+
pluginId,
|
|
186
|
+
displayName: descriptor?.displayName ?? "Agent total tokens",
|
|
187
|
+
value: null,
|
|
188
|
+
unit: measurement.unit ?? null,
|
|
189
|
+
availability: measurement.availability,
|
|
190
|
+
reason: measurement.reason,
|
|
191
|
+
provenance: known?.provenance ?? "unknown",
|
|
192
|
+
reliability: known?.reliability ?? "unknown",
|
|
193
|
+
variantId,
|
|
194
|
+
caseId: task.caseId,
|
|
195
|
+
glossaryAnchor: descriptor?.anchor ?? null,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return views;
|
|
201
|
+
}
|
|
202
|
+
/** Builds one evidence view per rendered metric, preserving report order. Pure; never mutates the report. */
|
|
203
|
+
export function buildMetricEvidenceViews(report, evidence) {
|
|
204
|
+
const pluginId = report.plugin.id;
|
|
205
|
+
const telemetry = warmIndexTelemetry(report);
|
|
206
|
+
const cumulative = warmIndexCumulativeTelemetry(report);
|
|
207
|
+
const views = report.metrics.map((metric) => {
|
|
208
|
+
const descriptor = resolveMetricDescriptor(pluginId, metric.id);
|
|
209
|
+
const parsed = metric.id.startsWith(AGENT_SUCCESS_PREFIX) && metric.description ? DESCRIPTION_PATTERN.exec(metric.description) : null;
|
|
210
|
+
let availability = metric.value === null ? "unavailable" : "available";
|
|
211
|
+
let reason = null;
|
|
212
|
+
if (parsed) {
|
|
213
|
+
availability = parsed[1];
|
|
214
|
+
reason = parsed[2] !== undefined && parsed[2].trim() !== "" ? parsed[2].trim() : null;
|
|
215
|
+
if (availability === "available")
|
|
216
|
+
reason = null;
|
|
217
|
+
}
|
|
218
|
+
const localId = pluginLocalMetricId(pluginId, metric.id);
|
|
219
|
+
const key = telemetryKey(metric.variantId ?? null, metric.caseId ?? null);
|
|
220
|
+
const tokenMetric = isTokenTelemetry(descriptor);
|
|
221
|
+
const known = !tokenMetric
|
|
222
|
+
? null
|
|
223
|
+
: pluginId === "agent-success-rate"
|
|
224
|
+
? agentSuccessTelemetry(localId, metric.caseId ?? null, metric.variantId ?? null, evidence)
|
|
225
|
+
: localId.startsWith("cumulative-")
|
|
226
|
+
? cumulative.get(key)
|
|
227
|
+
: telemetry.get(key);
|
|
228
|
+
return {
|
|
229
|
+
metricId: metric.id,
|
|
230
|
+
pluginId,
|
|
231
|
+
displayName: descriptor?.displayName ?? metric.name,
|
|
232
|
+
value: metric.value,
|
|
233
|
+
unit: metric.unit ?? descriptor?.unit ?? null,
|
|
234
|
+
availability,
|
|
235
|
+
reason,
|
|
236
|
+
provenance: tokenMetric ? (known ? known.provenance : "unknown") : basisProvenance(descriptor),
|
|
237
|
+
reliability: tokenMetric && known ? known.reliability : "unknown",
|
|
238
|
+
variantId: metric.variantId ?? null,
|
|
239
|
+
caseId: metric.caseId ?? null,
|
|
240
|
+
glossaryAnchor: descriptor?.anchor ?? null,
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
return [...views, ...warmUnavailableAgentTokenViews(report, telemetry)];
|
|
244
|
+
}
|
|
245
|
+
function groupByMetric(views) {
|
|
246
|
+
const groups = new Map();
|
|
247
|
+
for (const view of views) {
|
|
248
|
+
const key = `${view.pluginId}\u0000${view.metricId}`;
|
|
249
|
+
const group = groups.get(key) ?? { pluginId: view.pluginId, metricId: view.metricId, displayName: view.displayName, views: [] };
|
|
250
|
+
group.views.push(view);
|
|
251
|
+
groups.set(key, group);
|
|
252
|
+
}
|
|
253
|
+
return [...groups.values()];
|
|
254
|
+
}
|
|
255
|
+
function boundedList(values, limit) {
|
|
256
|
+
const sorted = values.map((value) => value.replace(/[.s]+$/, "")).sort();
|
|
257
|
+
const shown = sorted.slice(0, limit).join("; ");
|
|
258
|
+
return sorted.length > limit ? `${shown}; and ${sorted.length - limit} more` : shown;
|
|
259
|
+
}
|
|
260
|
+
function label(group) {
|
|
261
|
+
return `"${group.displayName}" (${group.metricId})`;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Deterministic, de-duplicated caveats derived from the views and from evidence the report already carries.
|
|
265
|
+
* Caveats supplement the report's own warnings and never change a scientific conclusion.
|
|
266
|
+
*/
|
|
267
|
+
export function buildMetricEvidenceCaveats(report, views) {
|
|
268
|
+
const byId = new Map();
|
|
269
|
+
const add = (caveat) => {
|
|
270
|
+
if (!byId.has(caveat.id))
|
|
271
|
+
byId.set(caveat.id, caveat);
|
|
272
|
+
};
|
|
273
|
+
for (const group of groupByMetric(views)) {
|
|
274
|
+
const total = group.views.length;
|
|
275
|
+
const descriptor = resolveMetricDescriptor(group.pluginId, group.metricId);
|
|
276
|
+
const base = { pluginId: group.pluginId, metricId: group.metricId };
|
|
277
|
+
const unavailable = group.views.filter((view) => view.availability === "unavailable");
|
|
278
|
+
if (unavailable.length > 0) {
|
|
279
|
+
const reasons = [...new Set(unavailable.map((view) => view.reason).filter((reason) => reason !== null))];
|
|
280
|
+
add({
|
|
281
|
+
...base,
|
|
282
|
+
id: `metric-unavailable:${group.pluginId}:${group.metricId}`,
|
|
283
|
+
kind: "metric-unavailable",
|
|
284
|
+
severity: "warning",
|
|
285
|
+
text: `${label(group)} is unavailable for ${unavailable.length} of ${total} measurement(s); unavailable is not zero.` +
|
|
286
|
+
(reasons.length > 0 ? ` Recorded reason(s): ${boundedList(reasons, MAX_REASONS)}.` : " No reason was recorded."),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const notApplicable = group.views.filter((view) => view.availability === "not-applicable");
|
|
290
|
+
if (notApplicable.length > 0) {
|
|
291
|
+
const reasons = [...new Set(notApplicable.map((view) => view.reason).filter((reason) => reason !== null))];
|
|
292
|
+
add({
|
|
293
|
+
...base,
|
|
294
|
+
id: `metric-not-applicable:${group.pluginId}:${group.metricId}`,
|
|
295
|
+
kind: "metric-not-applicable",
|
|
296
|
+
severity: "info",
|
|
297
|
+
text: `${label(group)} is not applicable for ${notApplicable.length} of ${total} measurement(s); this is not a zero or a failure.` +
|
|
298
|
+
(reasons.length > 0 ? ` Recorded reason(s): ${boundedList(reasons, MAX_REASONS)}.` : " No reason was recorded."),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
const estimated = group.views.filter((view) => view.provenance === "estimated" && view.availability === "available");
|
|
302
|
+
if (estimated.length > 0) {
|
|
303
|
+
add({
|
|
304
|
+
...base,
|
|
305
|
+
id: `estimated-not-provider-usage:${group.pluginId}:${group.metricId}`,
|
|
306
|
+
kind: "estimated-not-provider-usage",
|
|
307
|
+
severity: "info",
|
|
308
|
+
text: `${label(group)} is an estimate; it is not provider-reported usage and must not be compared to provider token totals.`,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (isTokenTelemetry(descriptor)) {
|
|
312
|
+
const available = group.views.filter((view) => view.availability === "available");
|
|
313
|
+
if (available.some((view) => view.provenance === "unknown")) {
|
|
314
|
+
add({
|
|
315
|
+
...base,
|
|
316
|
+
id: `telemetry-provenance-unknown:${group.pluginId}:${group.metricId}`,
|
|
317
|
+
kind: "telemetry-provenance-unknown",
|
|
318
|
+
severity: "warning",
|
|
319
|
+
text: `${label(group)}: the token telemetry source and reliability are not recorded in this report; the value is neither assumed provider-reported nor estimated.`,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
else if (available.some((view) => view.reliability === "unknown")) {
|
|
323
|
+
add({
|
|
324
|
+
...base,
|
|
325
|
+
id: `telemetry-reliability-unknown:${group.pluginId}:${group.metricId}`,
|
|
326
|
+
kind: "telemetry-reliability-unknown",
|
|
327
|
+
severity: "warning",
|
|
328
|
+
text: `${label(group)}: the token telemetry reliability is not recorded in this report.`,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const weak = group.views.filter((view) => view.reliability === "low" || view.reliability === "unavailable");
|
|
332
|
+
if (weak.length > 0) {
|
|
333
|
+
const levels = [...new Set(weak.map((view) => view.reliability))].sort().join(", ");
|
|
334
|
+
add({
|
|
335
|
+
...base,
|
|
336
|
+
id: `telemetry-reliability-low:${group.pluginId}:${group.metricId}`,
|
|
337
|
+
kind: "telemetry-reliability-low",
|
|
338
|
+
severity: "warning",
|
|
339
|
+
text: `${label(group)}: ${weak.length} of ${total} measurement(s) carry ${levels} telemetry reliability; interpret token comparisons cautiously.`,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const tokenStatus = report.warmIndexReuse?.agentCampaign?.tokenEvidenceStatus;
|
|
345
|
+
if (tokenStatus === "partial" || tokenStatus === "unavailable") {
|
|
346
|
+
add({
|
|
347
|
+
pluginId: report.plugin.id,
|
|
348
|
+
metricId: null,
|
|
349
|
+
id: `telemetry-partial:${report.plugin.id}`,
|
|
350
|
+
kind: "telemetry-partial",
|
|
351
|
+
severity: "warning",
|
|
352
|
+
text: tokenStatus === "partial"
|
|
353
|
+
? "Token evidence for the agent campaign is partial: some agent sides reported no token total, and missing totals are not zero."
|
|
354
|
+
: "Token evidence for the agent campaign is unavailable: no agent side reported a token total, and missing totals are not zero.",
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
const pluginId = report.plugin.id;
|
|
358
|
+
const missing = new Set();
|
|
359
|
+
for (const view of views) {
|
|
360
|
+
if (view.glossaryAnchor === null)
|
|
361
|
+
missing.add(pluginLocalMetricId(pluginId, view.metricId));
|
|
362
|
+
}
|
|
363
|
+
if (missing.size > 0) {
|
|
364
|
+
const ids = [...missing].sort();
|
|
365
|
+
const shown = ids.slice(0, MAX_LISTED).join(", ");
|
|
366
|
+
add({
|
|
367
|
+
pluginId,
|
|
368
|
+
metricId: null,
|
|
369
|
+
id: `glossary-missing:${pluginId}`,
|
|
370
|
+
kind: "glossary-missing",
|
|
371
|
+
severity: "info",
|
|
372
|
+
text: `Glossary metadata is missing for ${ids.length} rendered metric(s): ${shown}` +
|
|
373
|
+
(ids.length > MAX_LISTED ? `, and ${ids.length - MAX_LISTED} more` : "") +
|
|
374
|
+
". They are shown without a definition or glossary link.",
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return [...byId.values()].sort((a, b) => {
|
|
378
|
+
const rank = KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind);
|
|
379
|
+
if (rank !== 0)
|
|
380
|
+
return rank;
|
|
381
|
+
if (a.pluginId !== b.pluginId)
|
|
382
|
+
return a.pluginId < b.pluginId ? -1 : 1;
|
|
383
|
+
const am = a.metricId ?? "";
|
|
384
|
+
const bm = b.metricId ?? "";
|
|
385
|
+
if (am !== bm)
|
|
386
|
+
return am < bm ? -1 : 1;
|
|
387
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
388
|
+
});
|
|
389
|
+
}
|