@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
|
@@ -4,7 +4,13 @@ import { compareCodeUnits, interpretIndexManifest, readJson } from "./indexSnaps
|
|
|
4
4
|
export const AFFECTED_NEIGHBORHOOD_GRAPH_EVIDENCE_SCHEMA_VERSION = "my-dev-kit-lab-affected-neighborhood-graph-evidence-v1";
|
|
5
5
|
export const AFFECTED_NEIGHBORHOOD_SEED_MAPPING_SCHEMA_VERSION = "my-dev-kit-lab-affected-neighborhood-seed-mapping-v1";
|
|
6
6
|
const MANIFEST_FILE = "manifest.json";
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Schema 2 (my-dev-kit 1.13.x) identifies a symbol by file path + name. Schema 3 (my-dev-kit 1.14.x)
|
|
9
|
+
* additionally persists each symbol's actual graph `nodeId`, which may be a distinct `symbolq:` identity
|
|
10
|
+
* for same-named members. Both are understood; anything else is rejected.
|
|
11
|
+
*/
|
|
12
|
+
const SUPPORTED_SYMBOL_INDEX_SCHEMA_VERSIONS = ["2", "3"];
|
|
13
|
+
const SCHEMA_WITH_PERSISTED_SYMBOL_NODE_IDS = "3";
|
|
8
14
|
const SUPPORTED_CODE_GRAPH_ARTIFACT_KIND = "code-graph";
|
|
9
15
|
const SUPPORTED_CODE_GRAPH_VERSION_PATTERN = /^1\./;
|
|
10
16
|
// Same bounds as the v0.6.0 freshness/snapshot evidence lists.
|
|
@@ -57,18 +63,23 @@ function sortWarnings(warnings) {
|
|
|
57
63
|
return [...warnings].sort((left, right) => compareCodeUnits(left.code, right.code) || compareCodeUnits(left.subject ?? "", right.subject ?? "") || compareCodeUnits(left.message, right.message));
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
60
|
-
* Policy: reads indexed-file membership and baseline
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
66
|
+
* Policy: reads indexed-file membership and baseline symbol identities from a symbol-index artifact.
|
|
67
|
+
* Schema versions 2 and 3 are understood; anything else is unsupported. Under schema 2 a symbol's
|
|
68
|
+
* identity is its upstream file+name node ID. Under schema 3 it is the `nodeId` the index persisted,
|
|
69
|
+
* which is never reconstructed from the simple name: same-named members keep distinct identities,
|
|
70
|
+
* and a record without a usable `nodeId` stays visible as an unmappable identity with a warning
|
|
71
|
+
* instead of receiving a guessed one. A file entry whose symbol list is malformed keeps its
|
|
72
|
+
* membership but yields no symbol record; a malformed symbol entry is dropped with a warning. Pure.
|
|
64
73
|
*/
|
|
65
74
|
export function interpretSymbolIndexForNeighborhood(symbolIndex) {
|
|
66
75
|
if (!isRecord(symbolIndex)) {
|
|
67
76
|
return { ok: false, message: "Symbol index is not a JSON object." };
|
|
68
77
|
}
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
const schemaVersion = symbolIndex.schemaVersion;
|
|
79
|
+
if (typeof schemaVersion !== "string" || !SUPPORTED_SYMBOL_INDEX_SCHEMA_VERSIONS.includes(schemaVersion)) {
|
|
80
|
+
return { ok: false, message: `Symbol index schemaVersion is not a supported version (${SUPPORTED_SYMBOL_INDEX_SCHEMA_VERSIONS.join(", ")}).` };
|
|
71
81
|
}
|
|
82
|
+
const persistsNodeIds = schemaVersion === SCHEMA_WITH_PERSISTED_SYMBOL_NODE_IDS;
|
|
72
83
|
if (!Array.isArray(symbolIndex.files)) {
|
|
73
84
|
return { ok: false, message: "Symbol index does not list indexed files." };
|
|
74
85
|
}
|
|
@@ -92,29 +103,57 @@ export function interpretSymbolIndexForNeighborhood(symbolIndex) {
|
|
|
92
103
|
});
|
|
93
104
|
continue;
|
|
94
105
|
}
|
|
95
|
-
const
|
|
106
|
+
const identities = new Map();
|
|
96
107
|
const duplicates = new Set();
|
|
108
|
+
const missingNodeIdNames = new Set();
|
|
97
109
|
for (const symbol of entry.symbols) {
|
|
98
110
|
if (!isRecord(symbol) || !nonEmptyString(symbol.name)) {
|
|
99
111
|
warnings.push({ code: "invalid-symbol-entry", subject: boundedText(filePath), message: "Symbol index entry lacks a symbol name and was ignored." });
|
|
100
112
|
continue;
|
|
101
113
|
}
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
const name = symbol.name;
|
|
115
|
+
let identity;
|
|
116
|
+
let identityKey;
|
|
117
|
+
if (!persistsNodeIds) {
|
|
118
|
+
identity = { name, nodeId: symbolNodeId(filePath, name), persisted: false };
|
|
119
|
+
identityKey = identity.nodeId;
|
|
120
|
+
}
|
|
121
|
+
else if (nonEmptyString(symbol.nodeId)) {
|
|
122
|
+
identity = { name, nodeId: symbol.nodeId, persisted: true };
|
|
123
|
+
identityKey = `id:${symbol.nodeId}`;
|
|
104
124
|
}
|
|
105
|
-
|
|
125
|
+
else {
|
|
126
|
+
// No persisted identity: one unmappable identity per name, never a reconstructed one.
|
|
127
|
+
identity = { name, nodeId: null, persisted: true };
|
|
128
|
+
identityKey = `missing:${name}`;
|
|
129
|
+
missingNodeIdNames.add(name);
|
|
130
|
+
}
|
|
131
|
+
if (identities.has(identityKey)) {
|
|
132
|
+
duplicates.add(name);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
identities.set(identityKey, identity);
|
|
106
136
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
for (const name of [...missingNodeIdNames].sort(compareCodeUnits)) {
|
|
138
|
+
warnings.push({
|
|
139
|
+
code: "symbol-node-id-missing",
|
|
140
|
+
subject: boundedText(`${filePath}#${name}`),
|
|
141
|
+
message: "Schema-3 symbol record has no usable persisted nodeId; its identity is not guessed and it cannot be mapped to a graph node.",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const sortedRecords = [...identities.values()].sort((left, right) => compareCodeUnits(left.nodeId ?? "", right.nodeId ?? "") || compareCodeUnits(left.name, right.name));
|
|
145
|
+
const names = [...new Set(sortedRecords.map((record) => record.name))].sort(compareCodeUnits);
|
|
146
|
+
const sameName = names.filter((name) => sortedRecords.filter((record) => record.name === name).length > 1);
|
|
147
|
+
// Schema-2 evidence keeps its original shape: each name is its only identity, so no per-record data is added.
|
|
148
|
+
const fileRecord = { symbolNames: names, duplicateSymbolNames: [...duplicates].sort(compareCodeUnits) };
|
|
149
|
+
records.set(filePath, persistsNodeIds ? { ...fileRecord, records: sortedRecords, sameNameSymbolNames: sameName } : fileRecord);
|
|
111
150
|
}
|
|
112
151
|
const indexedFilePaths = [...paths].sort(compareCodeUnits);
|
|
113
152
|
const symbolFiles = new Map();
|
|
114
153
|
for (const key of [...records.keys()].sort(compareCodeUnits)) {
|
|
115
154
|
symbolFiles.set(key, records.get(key));
|
|
116
155
|
}
|
|
117
|
-
return { ok: true, value: { indexedFilePaths, symbolFiles, warnings } };
|
|
156
|
+
return { ok: true, value: { schemaVersion, indexedFilePaths, symbolFiles, warnings } };
|
|
118
157
|
}
|
|
119
158
|
/**
|
|
120
159
|
* Policy: validates the structure of a code-graph artifact and preserves its nodes and edges by
|
|
@@ -234,7 +273,7 @@ export async function loadAffectedNeighborhoodGraphEvidence(options) {
|
|
|
234
273
|
}
|
|
235
274
|
const symbolIndexSummary = {
|
|
236
275
|
path: normalizePath(path.relative(path.resolve(indexDir), symbolIndexFile)),
|
|
237
|
-
schemaVersion:
|
|
276
|
+
schemaVersion: symbols.value.schemaVersion,
|
|
238
277
|
};
|
|
239
278
|
const warnings = [...symbols.value.warnings];
|
|
240
279
|
let codeGraphSummary = null;
|
|
@@ -310,10 +349,35 @@ function lookupNode(evidence, expectedNodeId, kind) {
|
|
|
310
349
|
export function lookupFileNode(evidence, filePath) {
|
|
311
350
|
return lookupNode(evidence, fileNodeId(filePath), "file");
|
|
312
351
|
}
|
|
313
|
-
/**
|
|
352
|
+
/**
|
|
353
|
+
* Same-file symbol lookup by the upstream file+name identity. Never searches for a same-named symbol
|
|
354
|
+
* in another file. This is the schema-2 identity only; it does not find a qualified (`symbolq:`) member.
|
|
355
|
+
*/
|
|
314
356
|
export function lookupSymbolNode(evidence, filePath, symbolName) {
|
|
315
357
|
return lookupNode(evidence, symbolNodeId(filePath, symbolName), "symbol");
|
|
316
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Exact lookup of one baseline symbol identity. The node must exist under the identity the index
|
|
361
|
+
* actually recorded, be a `symbol` node, and (when the graph names a path) belong to the same
|
|
362
|
+
* file; otherwise it is unresolved. A record without a persisted identity is never mapped, and no
|
|
363
|
+
* identity is derived from its name.
|
|
364
|
+
*/
|
|
365
|
+
export function lookupSymbolRecordNode(evidence, filePath, record) {
|
|
366
|
+
if (record.nodeId === null)
|
|
367
|
+
return { ok: false, expectedNodeId: "", reason: "symbol-node-id-missing" };
|
|
368
|
+
const lookup = lookupNode(evidence, record.nodeId, "symbol");
|
|
369
|
+
if (!lookup.ok)
|
|
370
|
+
return lookup;
|
|
371
|
+
const node = evidence.nodes.get(lookup.nodeId);
|
|
372
|
+
if (node && node.path !== null && normalizePath(node.path) !== filePath) {
|
|
373
|
+
return { ok: false, expectedNodeId: record.nodeId, reason: "graph-node-path-mismatch" };
|
|
374
|
+
}
|
|
375
|
+
return lookup;
|
|
376
|
+
}
|
|
377
|
+
/** The baseline identities of one indexed file; evidence without per-record data keeps the schema-2 file+name identity. */
|
|
378
|
+
function symbolRecordsOf(filePath, file) {
|
|
379
|
+
return file.records ?? file.symbolNames.map((name) => ({ name, nodeId: symbolNodeId(filePath, name), persisted: false }));
|
|
380
|
+
}
|
|
317
381
|
/**
|
|
318
382
|
* Maps confirmed changed indexed files, and the baseline symbols belonging to them, onto baseline
|
|
319
383
|
* graph nodes. Pure and deterministic: consumes only the v0.6.0 snapshot and freshness assessment
|
|
@@ -404,18 +468,21 @@ export function mapAffectedNeighborhoodSeeds(input) {
|
|
|
404
468
|
continue;
|
|
405
469
|
}
|
|
406
470
|
if (record.duplicateSymbolNames.length > 0) {
|
|
407
|
-
warn("duplicate-symbol-records", `Baseline symbol index
|
|
471
|
+
warn("duplicate-symbol-records", `Baseline symbol index repeats ${record.duplicateSymbolNames.length} identical symbol record(s); each counts as one identity.`, filePath);
|
|
408
472
|
}
|
|
409
|
-
|
|
473
|
+
// Each distinct baseline identity is a seed in its own right: same-named members that the index
|
|
474
|
+
// persisted with different node IDs are never merged, and none is reconstructed from the name.
|
|
475
|
+
for (const identity of symbolRecordsOf(filePath, record)) {
|
|
476
|
+
const name = identity.name;
|
|
410
477
|
symbolIdentityCount += 1;
|
|
411
|
-
const symbolExpectedId =
|
|
478
|
+
const symbolExpectedId = identity.nodeId ?? "";
|
|
412
479
|
let symbolResolvedId = null;
|
|
413
480
|
let symbolReason = null;
|
|
414
481
|
if (!codeGraphLoaded) {
|
|
415
482
|
symbolReason = "graph-unavailable";
|
|
416
483
|
}
|
|
417
484
|
else {
|
|
418
|
-
const lookup =
|
|
485
|
+
const lookup = lookupSymbolRecordNode(graph, filePath, identity);
|
|
419
486
|
if (lookup.ok)
|
|
420
487
|
symbolResolvedId = lookup.nodeId;
|
|
421
488
|
else
|
|
@@ -426,7 +493,12 @@ export function mapAffectedNeighborhoodSeeds(input) {
|
|
|
426
493
|
seeds.add(symbolResolvedId);
|
|
427
494
|
}
|
|
428
495
|
else {
|
|
429
|
-
unresolved.push({
|
|
496
|
+
unresolved.push({
|
|
497
|
+
subject: "changed-symbol",
|
|
498
|
+
path: boundedText(filePath),
|
|
499
|
+
name: boundedText(name),
|
|
500
|
+
reason: symbolReason,
|
|
501
|
+
});
|
|
430
502
|
}
|
|
431
503
|
changedSymbols.push({
|
|
432
504
|
path: filePath,
|
|
@@ -490,11 +562,41 @@ function uniqueSorted(values) {
|
|
|
490
562
|
function normalizeExpectedPath(value) {
|
|
491
563
|
return normalizePath(value).replace(/^(\.\/)+/, "");
|
|
492
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* Candidate graph nodes for one expected symbol in one expected file. A request matches a baseline
|
|
567
|
+
* identity by its simple name or by that identity's exact persisted node ID; every matching identity
|
|
568
|
+
* is a distinct candidate (same-named members are never collapsed). A file the symbol index has no
|
|
569
|
+
* record for keeps the schema-2 file+name lookup. Nothing is derived from the name beyond that.
|
|
570
|
+
*/
|
|
571
|
+
function resolveExpectedSymbolInFile(graph, filePath, request) {
|
|
572
|
+
const file = graph.symbolFiles.get(filePath);
|
|
573
|
+
if (!file) {
|
|
574
|
+
const lookup = lookupSymbolNode(graph, filePath, request);
|
|
575
|
+
return { nodeIds: lookup.ok ? [lookup.nodeId] : [], identityMissing: false, pathMismatch: !lookup.ok && lookup.reason === "graph-node-path-mismatch" };
|
|
576
|
+
}
|
|
577
|
+
const nodeIds = [];
|
|
578
|
+
let identityMissing = false;
|
|
579
|
+
let pathMismatch = false;
|
|
580
|
+
for (const identity of symbolRecordsOf(filePath, file)) {
|
|
581
|
+
if (identity.name !== request && identity.nodeId !== request)
|
|
582
|
+
continue;
|
|
583
|
+
const lookup = lookupSymbolRecordNode(graph, filePath, identity);
|
|
584
|
+
if (lookup.ok)
|
|
585
|
+
nodeIds.push(lookup.nodeId);
|
|
586
|
+
else if (lookup.reason === "symbol-node-id-missing")
|
|
587
|
+
identityMissing = true;
|
|
588
|
+
else if (lookup.reason === "graph-node-path-mismatch")
|
|
589
|
+
pathMismatch = true;
|
|
590
|
+
}
|
|
591
|
+
return { nodeIds, identityMissing, pathMismatch };
|
|
592
|
+
}
|
|
493
593
|
/**
|
|
494
594
|
* Policy: maps a task's expected files and symbols to baseline graph nodes. Files resolve only to
|
|
495
595
|
* their exact `file:` node. A symbol resolves only within the task's own expected files and only
|
|
496
|
-
* when exactly one
|
|
497
|
-
*
|
|
596
|
+
* when exactly one matching identity exists there; zero matches is unresolved and several (in one
|
|
597
|
+
* file or across files, including same-named members with distinct persisted node IDs) is
|
|
598
|
+
* ambiguous (no winner is picked, no node is added). A request that is itself a persisted node ID
|
|
599
|
+
* resolves to exactly that identity. Never searches the rest of the graph. Pure.
|
|
498
600
|
*/
|
|
499
601
|
export function mapAffectedNeighborhoodTask(input) {
|
|
500
602
|
const { graph } = input;
|
|
@@ -561,15 +663,21 @@ export function mapAffectedNeighborhoodTask(input) {
|
|
|
561
663
|
const resolvedSymbols = [];
|
|
562
664
|
for (const name of expectedSymbols) {
|
|
563
665
|
// Scope is the task's expected files, whether or not their own file node resolved.
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
666
|
+
const found = expectedFiles.map((filePath) => resolveExpectedSymbolInFile(graph, filePath, name));
|
|
667
|
+
const candidates = uniqueSorted(found.flatMap((entry) => entry.nodeIds));
|
|
668
|
+
// A same-name identity that persisted no node ID cannot be excluded, so a lone mappable candidate
|
|
669
|
+
// is not trusted as "the" owner when such a sibling exists.
|
|
670
|
+
const identityMissing = found.some((entry) => entry.identityMissing);
|
|
671
|
+
if (candidates.length === 1 && !identityMissing) {
|
|
569
672
|
resolvedSymbols.push({ name, nodeId: candidates[0] });
|
|
570
673
|
}
|
|
571
|
-
else if (candidates.length
|
|
572
|
-
|
|
674
|
+
else if (candidates.length <= 1) {
|
|
675
|
+
const reason = identityMissing
|
|
676
|
+
? "symbol-node-id-missing"
|
|
677
|
+
: found.some((entry) => entry.pathMismatch)
|
|
678
|
+
? "graph-node-path-mismatch"
|
|
679
|
+
: "graph-node-missing";
|
|
680
|
+
unresolved.push({ subject: "expected-symbol", name: boundedText(name), reason });
|
|
573
681
|
}
|
|
574
682
|
else {
|
|
575
683
|
ambiguous.push({ name: boundedText(name), candidateNodeIds: candidates });
|
|
@@ -4,7 +4,8 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
6
6
|
import { isSameOrInside, resolvePhysicalPath } from "../../../evaluation/benchmarkSandbox/pathPolicy.js";
|
|
7
|
-
import { campaignAssert, campaignHash, canonicalCampaignJson, compareCampaignIdentity, validateCampaignIdentity } from "./campaignIdentity.js";
|
|
7
|
+
import { CAMPAIGN_IDENTITY_VERSION_V2, campaignAssert, campaignHash, canonicalCampaignJson, compareCampaignIdentity, validateCampaignIdentity } from "./campaignIdentity.js";
|
|
8
|
+
import { PROMPT_CONTRACT_ARTIFACT_FILE, renderPromptContractArtifact } from "./promptContract.js";
|
|
8
9
|
import { CAMPAIGN_CHECKPOINT_MAX_BYTES, requiredCampaignArtifacts, validateCampaignCheckpoint, validateCampaignCompletion, validateCampaignHistory, validateCampaignRelativePath } from "./campaignCheckpoint.js";
|
|
9
10
|
import { projectCampaignTreatmentEvidence } from "./campaignCheckpoint.js";
|
|
10
11
|
import { analyzeAgentSuccessTreatment } from "./analysis.js";
|
|
@@ -160,8 +161,29 @@ export async function prepareCampaignCompletion(boundary, identity, task, source
|
|
|
160
161
|
validateCampaignCompletion(result, identity, task, source.treatmentId);
|
|
161
162
|
return result;
|
|
162
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* A V2 identity binds the effective prompt contract. Every attempt directory that holds provider artifacts must also hold
|
|
166
|
+
* prompt-contract.json whose bytes equal the deterministic rendering of the identity's contract. The expectation comes from
|
|
167
|
+
* the checksummed identity, so the sidecar cannot vouch for itself and the closed V1 checkpoint schema is not extended.
|
|
168
|
+
*/
|
|
169
|
+
async function verifyPromptContractArtifacts(root, identity, references) {
|
|
170
|
+
if (identity.schemaVersion !== CAMPAIGN_IDENTITY_VERSION_V2)
|
|
171
|
+
return;
|
|
172
|
+
const expected = Buffer.from(renderPromptContractArtifact(identity.promptContract), "utf8");
|
|
173
|
+
const directories = new Set(references.filter((r) => r.kind === "provider").map((r) => path.posix.dirname(r.path)));
|
|
174
|
+
for (const directoryPath of [...directories].sort()) {
|
|
175
|
+
const bytes = await boundedRead(await safeFile(root, `${directoryPath}/${PROMPT_CONTRACT_ARTIFACT_FILE}`), 1024 * 1024);
|
|
176
|
+
campaignAssert(bytes.equals(expected), "PROMPT_CONTRACT_ARTIFACT");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
163
179
|
async function verifyArtifacts(boundary, checkpoint) {
|
|
164
180
|
const root = await validateRoot(boundary);
|
|
181
|
+
for (const unit of checkpoint.units)
|
|
182
|
+
await verifyPromptContractArtifacts(root, checkpoint.identity, unit.completion?.artifacts ?? []);
|
|
183
|
+
if (checkpoint.schemaVersion === CAMPAIGN_CHECKPOINT_SCHEMA_V2)
|
|
184
|
+
for (const unit of checkpoint.units)
|
|
185
|
+
for (const epoch of unit.epochs)
|
|
186
|
+
await verifyPromptContractArtifacts(root, checkpoint.identity, epoch.terminal?.artifacts ?? []);
|
|
165
187
|
for (const unit of checkpoint.units)
|
|
166
188
|
for (const reference of unit.completion?.artifacts ?? []) {
|
|
167
189
|
const bytes = await boundedRead(await safeFile(root, reference.path), 32 * 1024 * 1024);
|
|
@@ -20,6 +20,7 @@ import { buildAgentSuccessRateExecutionArtifact, validateAgentSuccessRateExecuti
|
|
|
20
20
|
import { buildAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateArtifactFamily, AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE } from "./analysisArtifact.js";
|
|
21
21
|
import { defaultContextDependencies, sealTask, writeAgentSuccessRateArtifactTargets } from "./plugin.js";
|
|
22
22
|
import { DEFAULT_AGENT_SUCCESS_KIT_COMMAND } from "./contextGeneration.js";
|
|
23
|
+
import { promptContractMatchesCurrentRenderer } from "./promptContract.js";
|
|
23
24
|
import { executeResumableCampaign } from "./campaignResumableExecution.js";
|
|
24
25
|
const preparations = new WeakMap();
|
|
25
26
|
async function establishPhysicalRoot(root) {
|
|
@@ -81,7 +82,7 @@ async function produceIdentity(input) {
|
|
|
81
82
|
projects.push({ projectId, files });
|
|
82
83
|
}
|
|
83
84
|
const provider = input.provider;
|
|
84
|
-
const allowed = provider.kind === "deterministic-fixture" ? ["kind"] : provider.kind === "trusted-simulated-provider" ? ["kind", "simulationId", "providerId", "timeoutMs", "repairAttempts", "runAgent", "contextDependencies"] : ["kind", "providerId", "timeoutMs", "repairAttempts", "includeRealAgents", "trustedExecutable", "kitCommand", "contextDependencies", "env"];
|
|
85
|
+
const allowed = provider.kind === "deterministic-fixture" ? ["kind"] : provider.kind === "trusted-simulated-provider" ? ["kind", "simulationId", "providerId", "timeoutMs", "repairAttempts", "runAgent", "contextDependencies", "promptContract"] : ["kind", "providerId", "timeoutMs", "repairAttempts", "includeRealAgents", "trustedExecutable", "kitCommand", "contextDependencies", "env", "promptContract"];
|
|
85
86
|
campaignAssert(Object.keys(provider).every(key => allowed.includes(key)), "INCONSISTENT_EXECUTION_MODE");
|
|
86
87
|
const simulated = provider.kind === "trusted-simulated-provider";
|
|
87
88
|
campaignAssert(["deterministic-fixture", "trusted-simulated-provider", "real-agent"].includes(provider.kind), "INVALID_EXECUTION_MODE");
|
|
@@ -102,6 +103,9 @@ async function produceIdentity(input) {
|
|
|
102
103
|
await rm(commandsDir, { recursive: true, force: true });
|
|
103
104
|
}
|
|
104
105
|
}
|
|
106
|
+
const promptContract = provider.kind === "deterministic-fixture" ? null : provider.promptContract ?? null;
|
|
107
|
+
if (promptContract !== null && provider.kind !== "deterministic-fixture")
|
|
108
|
+
campaignAssert(promptContract.providerId === provider.providerId && promptContractMatchesCurrentRenderer(promptContract), "PROMPT_CONTRACT_UNVERIFIABLE");
|
|
105
109
|
const functions = (object) => Object.entries(object).map(([key, value]) => ({ key, source: String(value) })).sort((a, b) => a.key < b.key ? -1 : 1);
|
|
106
110
|
return createCampaignIdentity({
|
|
107
111
|
tasks: input.tasks,
|
|
@@ -109,6 +113,7 @@ async function produceIdentity(input) {
|
|
|
109
113
|
config: provider.kind === "deterministic-fixture" ? { providerId: provider.kind, timeoutMs: 1000, repairAttempts: 0 } : { providerId: provider.providerId, timeoutMs: provider.timeoutMs, repairAttempts: provider.repairAttempts },
|
|
110
114
|
contextConfiguration: { contract: "existing-agent-success-context-v1", treatments: [...input.treatments] },
|
|
111
115
|
retrievalConfiguration: { contract: simulated ? "trusted-simulated-retrieval-v1" : real ? "real-provider-retrieval-v1" : "no-retrieval-fixture-v1", dependencies: simulated ? functions(provider.contextDependencies) : real ? functions(provider.contextDependencies ?? defaultContextDependencies()) : [], ...(real ? { kitCommand: provider.kitCommand ?? DEFAULT_AGENT_SUCCESS_KIT_COMMAND, version: retrievalVersion } : {}), executionDependencies: functions(input.dependencies ?? {}) },
|
|
116
|
+
promptContract,
|
|
112
117
|
verifiedProviderObservation: simulated ? Buffer.from(canonicalCampaignJson({ contract: "trusted-simulated-provider-v1", simulationId: provider.simulationId, implementation: String(provider.runAgent) })) : observed
|
|
113
118
|
});
|
|
114
119
|
}
|
|
@@ -206,7 +211,7 @@ export async function executeAgentSuccessCampaign(prepared, args) {
|
|
|
206
211
|
const result = provider.kind === "trusted-simulated-provider" && contexts ? await executeAgentSuccessRepairTreatment({ ...common, repair: {
|
|
207
212
|
providerId: provider.providerId, repairAttempts: provider.repairAttempts,
|
|
208
213
|
createProposalSource: ({ attemptNumber, feedback }) => createRealAgentProposalSource({
|
|
209
|
-
settings: { providerId: provider.providerId, timeoutMs: provider.timeoutMs, outDir: boundary.outputRoot, privateRoots: common.privateRoots, protectedRoots: [boundary.toolRoot, boundary.outputRoot, runtimeRoot, canonicalProjectRoot],
|
|
214
|
+
settings: { providerId: provider.providerId, timeoutMs: provider.timeoutMs, ...(provider.promptContract ? { promptContract: provider.promptContract } : {}), outDir: boundary.outputRoot, privateRoots: common.privateRoots, protectedRoots: [boundary.toolRoot, boundary.outputRoot, runtimeRoot, canonicalProjectRoot],
|
|
210
215
|
runAgent: async (request) => {
|
|
211
216
|
await assertCampaignDispatch(boundary, identity, ownership);
|
|
212
217
|
await prepareCampaignProviderOutput(boundary, identity, ownership, agentSuccessAgentArtifactDirectory(task.benchmarkProject, task.id, unit.treatmentId, attemptNumber));
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { validateAgentSuccessTask } from "../../../evaluation/agentSuccess/index.js";
|
|
3
3
|
import { AGENT_SUCCESS_RATE_TREATMENT_IDS } from "./metadata.js";
|
|
4
|
+
import { assertPromptContractShape, promptContractDigest } from "./promptContract.js";
|
|
4
5
|
export const CAMPAIGN_IDENTITY_VERSION = "my-dev-kit-lab-agent-success-rate-campaign-identity-v1";
|
|
6
|
+
/** V2 extends V1 with the normalized prompt contract and its digest; V1 evidence stays readable and unchanged. */
|
|
7
|
+
export const CAMPAIGN_IDENTITY_VERSION_V2 = "my-dev-kit-lab-agent-success-rate-campaign-identity-v2";
|
|
5
8
|
export const CAMPAIGN_COMPONENTS = ["corpus", "tasks", "projects", "checks", "behavior", "context", "retrieval", "execution", "providerBuild"];
|
|
6
9
|
export const CAMPAIGN_SOURCE_FILES = ["src/agents/types.ts", "src/agents/normalizeAgentTelemetry.ts", "src/agents/parseAgentTokenUsage.ts", "src/agents/runAgentPrompt.ts", "src/agents/adapters/codexAdapter.ts", "src/agents/adapters/claudeAdapter.ts", "src/evaluation/classifyAgentRunOutcome.ts", "src/experiments/plugins/agentSuccessRate/execution.ts", "src/experiments/plugins/agentSuccessRate/realAgentExecution.ts", "src/experiments/plugins/agentSuccessRate/contextGeneration.ts", "src/experiments/plugins/agentSuccessRate/repairExecution.ts", "src/experiments/plugins/agentSuccessRate/repairPolicy.ts", "src/experiments/plugins/agentSuccessRate/analysis.ts", "src/experiments/plugins/agentSuccessRate/plugin.ts"];
|
|
7
10
|
/** Additional transitive execution/safety owners; a missing source contract cannot certify reuse. */
|
|
8
|
-
export const CAMPAIGN_DEPENDENCY_FILES = ["src/experiments/plugins/agentSuccessRate/executionTypes.ts", "src/experiments/plugins/agentSuccessRate/analysisTypes.ts", "src/experiments/plugins/agentSuccessRate/attemptEvidence.ts", "src/experiments/plugins/agentSuccessRate/campaignIdentity.ts", "src/experiments/plugins/agentSuccessRate/campaignCheckpoint.ts", "src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.ts", "src/evaluation/agentSuccess/patchTypes.ts", "src/evaluation/agentSuccess/taskTypes.ts", "src/evaluation/agentSuccess/verificationTypes.ts", "src/evaluation/agentSuccess/corpusTypes.ts", "src/evaluation/benchmarkSandbox/types.ts", "src/evaluation/benchmarkSandbox/errors.ts", "src/evaluation/changeSet/types.ts", "src/agents/agentRegistry.ts", "src/core/pathSafety.ts", "src/core/runMeasuredCommand.ts", "src/core/resolveCommand.ts", "src/core/processTree.ts", "src/core/fileGlobs.ts", "src/core/countTokens.ts", "src/core/commandLine.ts", "src/runtime/managedProcess.ts", "src/evaluation/runMyDevKitRetrieval.ts", "src/evaluation/agentSuccess/assessBaseline.ts", "src/evaluation/agentSuccess/applyPatchToSandbox.ts", "src/evaluation/agentSuccess/extractPatchCandidate.ts", "src/evaluation/agentSuccess/parseUnifiedDiff.ts", "src/evaluation/agentSuccess/runVerificationCheck.ts", "src/evaluation/agentSuccess/taskPaths.ts", "src/evaluation/agentSuccess/validatePatchPolicy.ts", "src/evaluation/agentSuccess/validateAgentSuccessTask.ts", "src/evaluation/agentSuccess/readAgentSuccessCorpus.ts", "src/evaluation/agentSuccess/validateAgentSuccessCorpus.ts", "src/evaluation/benchmarkSandbox/createBenchmarkSandbox.ts", "src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.ts", "src/evaluation/benchmarkSandbox/pathPolicy.ts", "src/evaluation/benchmarkSandbox/treeSnapshot.ts", "src/evaluation/benchmarkSandbox/gitExecutor.ts", "src/evaluation/benchmarkSandbox/minimalHostEnv.ts", "src/evaluation/changeSet/captureChangeSet.ts", "src/experiments/plugins/agentSuccessRate/agentTaskProjection.ts", "src/experiments/plugins/agentSuccessRate/realAgentPrompt.ts", "src/experiments/plugins/agentSuccessRate/repairFeedback.ts", "src/experiments/plugins/agentSuccessRate/repairAnalysis.ts", "src/experiments/plugins/agentSuccessRate/metadata.ts", "src/experiments/plugins/agentSuccessRate/config.ts", "src/experiments/plugins/agentSuccessRate/executionArtifact.ts", "src/experiments/plugins/agentSuccessRate/analysisArtifact.ts", "src/experiments/outputPaths.ts"];
|
|
11
|
+
export const CAMPAIGN_DEPENDENCY_FILES = ["src/experiments/plugins/agentSuccessRate/executionTypes.ts", "src/experiments/plugins/agentSuccessRate/analysisTypes.ts", "src/experiments/plugins/agentSuccessRate/attemptEvidence.ts", "src/experiments/plugins/agentSuccessRate/campaignIdentity.ts", "src/experiments/plugins/agentSuccessRate/campaignCheckpoint.ts", "src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.ts", "src/evaluation/agentSuccess/patchTypes.ts", "src/evaluation/agentSuccess/taskTypes.ts", "src/evaluation/agentSuccess/verificationTypes.ts", "src/evaluation/agentSuccess/corpusTypes.ts", "src/evaluation/benchmarkSandbox/types.ts", "src/evaluation/benchmarkSandbox/errors.ts", "src/evaluation/changeSet/types.ts", "src/agents/agentRegistry.ts", "src/core/pathSafety.ts", "src/core/runMeasuredCommand.ts", "src/core/resolveCommand.ts", "src/core/processTree.ts", "src/core/fileGlobs.ts", "src/core/countTokens.ts", "src/core/commandLine.ts", "src/runtime/managedProcess.ts", "src/evaluation/runMyDevKitRetrieval.ts", "src/evaluation/agentSuccess/assessBaseline.ts", "src/evaluation/agentSuccess/applyPatchToSandbox.ts", "src/evaluation/agentSuccess/extractPatchCandidate.ts", "src/evaluation/agentSuccess/parseUnifiedDiff.ts", "src/evaluation/agentSuccess/runVerificationCheck.ts", "src/evaluation/agentSuccess/taskPaths.ts", "src/evaluation/agentSuccess/validatePatchPolicy.ts", "src/evaluation/agentSuccess/validateAgentSuccessTask.ts", "src/evaluation/agentSuccess/readAgentSuccessCorpus.ts", "src/evaluation/agentSuccess/validateAgentSuccessCorpus.ts", "src/evaluation/benchmarkSandbox/createBenchmarkSandbox.ts", "src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.ts", "src/evaluation/benchmarkSandbox/pathPolicy.ts", "src/evaluation/benchmarkSandbox/treeSnapshot.ts", "src/evaluation/benchmarkSandbox/gitExecutor.ts", "src/evaluation/benchmarkSandbox/minimalHostEnv.ts", "src/evaluation/changeSet/captureChangeSet.ts", "src/experiments/plugins/agentSuccessRate/agentTaskProjection.ts", "src/experiments/plugins/agentSuccessRate/realAgentPrompt.ts", "src/experiments/plugins/agentSuccessRate/promptContract.ts", "src/experiments/plugins/agentSuccessRate/strictDiffResponse.ts", "src/experiments/plugins/agentSuccessRate/repairFeedback.ts", "src/experiments/plugins/agentSuccessRate/repairAnalysis.ts", "src/experiments/plugins/agentSuccessRate/metadata.ts", "src/experiments/plugins/agentSuccessRate/config.ts", "src/experiments/plugins/agentSuccessRate/executionArtifact.ts", "src/experiments/plugins/agentSuccessRate/analysisArtifact.ts", "src/experiments/outputPaths.ts"];
|
|
9
12
|
export function campaignAssert(condition, code = "INVALID_CHECKPOINT") {
|
|
10
13
|
if (!condition)
|
|
11
14
|
throw new Error(code);
|
|
@@ -42,8 +45,10 @@ export function campaignExact(value, keys) {
|
|
|
42
45
|
campaignAssert(Object.keys(value).sort().join("\0") === [...keys].sort().join("\0"));
|
|
43
46
|
}
|
|
44
47
|
export function validateCampaignIdentity(value) {
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
const v2 = typeof value === "object" && value !== null && value.schemaVersion === CAMPAIGN_IDENTITY_VERSION_V2;
|
|
49
|
+
const baseKeys = ["schemaVersion", "algorithm", "pluginContract", "digest", "selected", "treatmentOrder", "config", "components"];
|
|
50
|
+
campaignExact(value, v2 ? [...baseKeys, "promptContract", "promptContractDigest"] : baseKeys);
|
|
51
|
+
campaignAssert(value.schemaVersion === (v2 ? CAMPAIGN_IDENTITY_VERSION_V2 : CAMPAIGN_IDENTITY_VERSION) && value.algorithm === "sha256" && value.pluginContract === "agent-success-rate/1.0.0", "INCOMPATIBLE_IDENTITY");
|
|
47
52
|
campaignAssert(Array.isArray(value.selected) && value.selected.length > 0 && value.selected.length <= 1000);
|
|
48
53
|
const ids = new Set();
|
|
49
54
|
for (const row of value.selected) {
|
|
@@ -60,6 +65,17 @@ export function validateCampaignIdentity(value) {
|
|
|
60
65
|
campaignExact(component, ["version", "digest"]);
|
|
61
66
|
campaignAssert(component.version === "1" && (component.digest === null || isCampaignDigest(component.digest)));
|
|
62
67
|
}
|
|
68
|
+
if (v2) {
|
|
69
|
+
try {
|
|
70
|
+
assertPromptContractShape(value.promptContract);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
campaignAssert(false, "PROMPT_CONTRACT_INVALID");
|
|
74
|
+
}
|
|
75
|
+
const contract = value.promptContract;
|
|
76
|
+
campaignAssert(contract.providerId === value.config.providerId, "PROMPT_CONTRACT_PROVIDER_MISMATCH");
|
|
77
|
+
campaignAssert(isCampaignDigest(value.promptContractDigest) && value.promptContractDigest === promptContractDigest(contract), "PROMPT_CONTRACT_DIGEST");
|
|
78
|
+
}
|
|
63
79
|
const { digest, ...body } = value;
|
|
64
80
|
campaignAssert(isCampaignDigest(digest) && digest === campaignHash(canonicalCampaignJson(body)), "IDENTITY_INTEGRITY");
|
|
65
81
|
}
|
|
@@ -83,7 +99,12 @@ export function createCampaignIdentity(input) {
|
|
|
83
99
|
projects = digest(input.projects.map((p) => ({ projectId: p.projectId, files: manifest(p.files) })).sort((a, b) => a.projectId < b.projectId ? -1 : a.projectId > b.projectId ? 1 : 0));
|
|
84
100
|
}
|
|
85
101
|
const values = { corpus: input.corpusBytes === null ? null : campaignHash(input.corpusBytes), tasks: digest(input.tasks), projects, checks: digest(input.tasks.map((t) => ({ id: t.id, task: t.taskChecks, regression: t.regressionChecks, protected: t.protectedFiles }))), behavior: digest(input.tasks.map((t) => ({ id: t.id, facts: t.behaviorFacts, expected: t.expectedEditFiles, allowed: t.allowedEditFiles }))), context: input.contextConfiguration === null ? null : digest(input.contextConfiguration), retrieval: input.retrievalConfiguration === null ? null : digest(input.retrievalConfiguration), execution: source, providerBuild: input.config.providerId === "deterministic-fixture" ? digest("no-provider-invocation-v1") : input.verifiedProviderObservation === null ? null : campaignHash(input.verifiedProviderObservation) };
|
|
86
|
-
const
|
|
102
|
+
const contract = input.promptContract ?? null;
|
|
103
|
+
if (contract !== null) {
|
|
104
|
+
assertPromptContractShape(contract);
|
|
105
|
+
campaignAssert(input.config.providerId !== "deterministic-fixture" && contract.providerId === input.config.providerId, "PROMPT_CONTRACT_PROVIDER_MISMATCH");
|
|
106
|
+
}
|
|
107
|
+
const body = { schemaVersion: contract === null ? CAMPAIGN_IDENTITY_VERSION : CAMPAIGN_IDENTITY_VERSION_V2, algorithm: "sha256", pluginContract: "agent-success-rate/1.0.0", selected: input.tasks.map((t) => ({ caseId: t.id, projectId: t.benchmarkProject, taskDigest: digest(t) })), treatmentOrder: [...AGENT_SUCCESS_RATE_TREATMENT_IDS], config: structuredClone(input.config), components: Object.fromEntries(CAMPAIGN_COMPONENTS.map((key) => [key, { version: "1", digest: values[key] }])), ...(contract === null ? {} : { promptContract: structuredClone(contract), promptContractDigest: promptContractDigest(contract) }) };
|
|
87
108
|
const result = { ...body, digest: digest(body) };
|
|
88
109
|
validateCampaignIdentity(result);
|
|
89
110
|
return result;
|
|
@@ -96,6 +117,10 @@ export function compareCampaignIdentity(left, right) {
|
|
|
96
117
|
catch {
|
|
97
118
|
return "UNVERIFIABLE";
|
|
98
119
|
}
|
|
120
|
+
if (left.schemaVersion !== right.schemaVersion)
|
|
121
|
+
return "MISMATCH";
|
|
122
|
+
if (left.schemaVersion === CAMPAIGN_IDENTITY_VERSION_V2 && right.schemaVersion === CAMPAIGN_IDENTITY_VERSION_V2 && (canonicalCampaignJson(left.promptContract) !== canonicalCampaignJson(right.promptContract) || left.promptContractDigest !== right.promptContractDigest))
|
|
123
|
+
return "MISMATCH";
|
|
99
124
|
if (canonicalCampaignJson(left.selected) !== canonicalCampaignJson(right.selected) || canonicalCampaignJson(left.config) !== canonicalCampaignJson(right.config))
|
|
100
125
|
return "MISMATCH";
|
|
101
126
|
let missing = false;
|
|
@@ -119,7 +119,7 @@ export async function executeResumableCampaign(prepared, args) {
|
|
|
119
119
|
campaignAssert(provider.kind === "deterministic-fixture" || contexts !== null, "PROVIDER_CONTEXT_REQUIRED");
|
|
120
120
|
const result = provider.kind !== "deterministic-fixture" && contexts ? await executeAgentSuccessRepairTreatment({ ...common, repair: {
|
|
121
121
|
providerId: provider.providerId, repairAttempts: provider.repairAttempts,
|
|
122
|
-
createProposalSource: ({ attemptNumber, feedback }) => createRealAgentProposalSource({ settings: { providerId: provider.providerId, timeoutMs: provider.timeoutMs, ...(provider.kind === "real-agent" ? { trustedExecutable: provider.trustedExecutable, env: provider.env } : {}), outDir: epochRoot, privateRoots: common.privateRoots, protectedRoots: [boundary.toolRoot, boundary.outputRoot, activeRuntime, canonicalProjectRoot], runAgent: async (request) => {
|
|
122
|
+
createProposalSource: ({ attemptNumber, feedback }) => createRealAgentProposalSource({ settings: { providerId: provider.providerId, timeoutMs: provider.timeoutMs, ...(provider.promptContract ? { promptContract: provider.promptContract } : {}), ...(provider.kind === "real-agent" ? { trustedExecutable: provider.trustedExecutable, env: provider.env } : {}), outDir: epochRoot, privateRoots: common.privateRoots, protectedRoots: [boundary.toolRoot, boundary.outputRoot, activeRuntime, canonicalProjectRoot], runAgent: async (request) => {
|
|
123
123
|
await fresh();
|
|
124
124
|
campaignAssert((await read()).checkpoint?.integrity === claimed.integrity, "STALE_INVOCATION_CLAIM");
|
|
125
125
|
await prepareCampaignProviderOutput(boundary, identity, owner, `${epoch.namespace}/${agentSuccessAgentArtifactDirectory(unit.task.benchmarkProject, unit.task.id, unit.treatmentId, attemptNumber)}`);
|
|
@@ -2,12 +2,37 @@ import { parseCommandString } from "../../../core/commandLine.js";
|
|
|
2
2
|
import { invalidExperimentConfig, isPlainObject, mergeConfig, validExperimentConfig } from "../../config.js";
|
|
3
3
|
import { DEFAULT_AGENT_SUCCESS_KIT_COMMAND } from "./contextGeneration.js";
|
|
4
4
|
import { AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS } from "./realAgentExecution.js";
|
|
5
|
+
import { createPromptContract } from "./promptContract.js";
|
|
6
|
+
import { AGENT_SUCCESS_PROMPT_EXPLANATION_MODES, AGENT_SUCCESS_PROMPT_LENGTH_MODES, AGENT_SUCCESS_PROMPT_TEMPLATE_IDS, AGENT_SUCCESS_PROMPT_TOOL_USE_MODES } from "./realAgentPrompt.js";
|
|
5
7
|
export const AGENT_SUCCESS_RATE_REAL_AGENT_IDS = ["codex", "claude"];
|
|
8
|
+
const PROMPT_FIELD_ALLOWED_VALUES = {
|
|
9
|
+
promptTemplate: AGENT_SUCCESS_PROMPT_TEMPLATE_IDS,
|
|
10
|
+
promptLength: AGENT_SUCCESS_PROMPT_LENGTH_MODES,
|
|
11
|
+
promptExplanation: AGENT_SUCCESS_PROMPT_EXPLANATION_MODES,
|
|
12
|
+
promptToolUse: AGENT_SUCCESS_PROMPT_TOOL_USE_MODES
|
|
13
|
+
};
|
|
14
|
+
const PROMPT_FIELDS = [...Object.keys(PROMPT_FIELD_ALLOWED_VALUES), "maxCommandCount"];
|
|
15
|
+
/** True when any prompt-contract option was explicitly supplied (even with its default value). */
|
|
16
|
+
export function hasExplicitPromptContractSelection(config) {
|
|
17
|
+
return PROMPT_FIELDS.some((field) => config[field] !== undefined);
|
|
18
|
+
}
|
|
19
|
+
/** The explicit prompt contract for a validated real-agent config, or null when no prompt option was supplied. */
|
|
20
|
+
export function resolveAgentSuccessPromptContract(config) {
|
|
21
|
+
if (config.agentId === undefined || !hasExplicitPromptContractSelection(config))
|
|
22
|
+
return null;
|
|
23
|
+
return createPromptContract(config.agentId, {
|
|
24
|
+
...(config.promptTemplate !== undefined ? { templateId: config.promptTemplate } : {}),
|
|
25
|
+
...(config.promptLength !== undefined ? { lengthMode: config.promptLength } : {}),
|
|
26
|
+
...(config.promptExplanation !== undefined ? { explanationMode: config.promptExplanation } : {}),
|
|
27
|
+
...(config.promptToolUse !== undefined ? { toolUseMode: config.promptToolUse } : {}),
|
|
28
|
+
...(config.maxCommandCount !== undefined ? { maxCommandCount: config.maxCommandCount } : {})
|
|
29
|
+
});
|
|
30
|
+
}
|
|
6
31
|
export const AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES = [0, 1, 2];
|
|
7
32
|
export const defaultAgentSuccessRateConfig = {
|
|
8
33
|
outDir: "lab-output/agent-success-rate"
|
|
9
34
|
};
|
|
10
|
-
const SUPPORTED_FIELDS = ["outDir", "caseIds", "benchmarkProjects", "agentId", "includeRealAgents", "timeoutMs", "kitCommand", "repairAttempts"];
|
|
35
|
+
const SUPPORTED_FIELDS = ["outDir", "caseIds", "benchmarkProjects", "agentId", "includeRealAgents", "timeoutMs", "kitCommand", "repairAttempts", ...PROMPT_FIELDS];
|
|
11
36
|
/** Names that are recognized only to give a precise rejection. */
|
|
12
37
|
const REJECTED_FIELD_MESSAGES = {
|
|
13
38
|
commandTemplate: "commandTemplate is not supported; real providers use the fixed adapter invocation.",
|
|
@@ -31,7 +56,12 @@ export const agentSuccessRateConfigDefinition = {
|
|
|
31
56
|
{ name: "includeRealAgents", type: "boolean", description: "Must be exactly true whenever agentId is set; rejected otherwise." },
|
|
32
57
|
{ name: "timeoutMs", type: "number", description: "Per-attempt provider timeout in ms (real-agent mode only)." },
|
|
33
58
|
{ name: "kitCommand", type: "string", description: "my-dev-kit command used for context-pack retrieval (real-agent mode only)." },
|
|
34
|
-
{ name: "repairAttempts", type: "number", description: "Repairs allowed after the initial attempt: 0 (default), 1 or 2. Real-agent mode only; at most 3 attempts per treatment." }
|
|
59
|
+
{ name: "repairAttempts", type: "number", description: "Repairs allowed after the initial attempt: 0 (default), 1 or 2. Real-agent mode only; at most 3 attempts per treatment." },
|
|
60
|
+
{ name: "promptTemplate", type: "string", description: "Prompt template: legacy-diff-v1 (default) or strict-diff-v1. Real-agent mode only." },
|
|
61
|
+
{ name: "promptLength", type: "string", description: "Prompt length: standard (default) or short. Real-agent mode only." },
|
|
62
|
+
{ name: "promptExplanation", type: "string", description: "Explanation policy: legacy (default) or none. Real-agent mode only." },
|
|
63
|
+
{ name: "promptToolUse", type: "string", description: "Tool-use statement: prohibited (default) or bounded (explicit zero command budget). Real-agent mode only." },
|
|
64
|
+
{ name: "maxCommandCount", type: "number", description: "Maximum command count guidance; only 0 is accepted. Real-agent mode only." }
|
|
35
65
|
]
|
|
36
66
|
};
|
|
37
67
|
export function validateAgentSuccessRateConfig(config) {
|
|
@@ -116,6 +146,21 @@ export function validateAgentSuccessRateConfig(config) {
|
|
|
116
146
|
normalized.kitCommand = DEFAULT_AGENT_SUCCESS_KIT_COMMAND;
|
|
117
147
|
}
|
|
118
148
|
}
|
|
149
|
+
const suppliedPromptFields = PROMPT_FIELDS.filter((field) => raw[field] !== undefined);
|
|
150
|
+
if (suppliedPromptFields.length > 0) {
|
|
151
|
+
if (agentId === undefined || raw.includeRealAgents !== true) {
|
|
152
|
+
errors.push(`${suppliedPromptFields.join(", ")} require real-agent mode (agentId together with includeRealAgents=true); prompt options never enable a provider.`);
|
|
153
|
+
}
|
|
154
|
+
for (const [field, allowed] of Object.entries(PROMPT_FIELD_ALLOWED_VALUES)) {
|
|
155
|
+
const value = raw[field];
|
|
156
|
+
if (value !== undefined && !(typeof value === "string" && allowed.includes(value))) {
|
|
157
|
+
errors.push(`${field} must be one of: ${allowed.join(", ")}.`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (raw.maxCommandCount !== undefined && !(typeof raw.maxCommandCount === "number" && Number.isInteger(raw.maxCommandCount) && raw.maxCommandCount === 0)) {
|
|
161
|
+
errors.push("maxCommandCount must be the integer 0; the benchmark permits no commands.");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
119
164
|
return errors.length > 0 ? invalidExperimentConfig(errors) : validExperimentConfig(normalized);
|
|
120
165
|
}
|
|
121
166
|
function isRepairAttemptsValue(value) {
|
|
@@ -9,7 +9,7 @@ import { summarizeExperimentRun } from "../../results.js";
|
|
|
9
9
|
import { analyzeAgentSuccessRate } from "./analysis.js";
|
|
10
10
|
import { projectAgentFacingTask } from "./agentTaskProjection.js";
|
|
11
11
|
import { AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE, buildAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateArtifactFamily } from "./analysisArtifact.js";
|
|
12
|
-
import { agentSuccessRateConfigDefinition, defaultAgentSuccessRateConfig, validateAgentSuccessRateConfig } from "./config.js";
|
|
12
|
+
import { agentSuccessRateConfigDefinition, defaultAgentSuccessRateConfig, resolveAgentSuccessPromptContract, validateAgentSuccessRateConfig } from "./config.js";
|
|
13
13
|
import { AgentSuccessPackContextBuilder, DEFAULT_AGENT_SUCCESS_KIT_COMMAND, buildContextTaskInput, buildRawFullFileContext, collectForbiddenAgentValues } from "./contextGeneration.js";
|
|
14
14
|
import { AGENT_SUCCESS_RATE_SELF_ONLY_MESSAGE, AgentSuccessRateInputError, boundMessage, executeAgentSuccessCase, readAgentSuccessTasksInput, resolveControlledBenchmarkProject, resolveSandboxRuntimeRoot, selectAgentSuccessTasks } from "./execution.js";
|
|
15
15
|
import { AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE, buildAgentSuccessRateExecutionArtifact, validateAgentSuccessRateExecutionArtifact } from "./executionArtifact.js";
|
|
@@ -83,6 +83,7 @@ export const agentSuccessRatePlugin = {
|
|
|
83
83
|
timeoutMs: real.timeoutMs,
|
|
84
84
|
env: readAgentEnvInput(inputs),
|
|
85
85
|
runAgent: readRunAgentInput(inputs),
|
|
86
|
+
...(real.promptContract ? { promptContract: real.promptContract } : {}),
|
|
86
87
|
outDir,
|
|
87
88
|
protectedRoots: [context.toolRoot, outDir, runtimeRoot, packageRoot(), ...projectRoots.values()],
|
|
88
89
|
privateRoots: [outDir, context.toolRoot],
|
|
@@ -399,7 +400,8 @@ function readRealAgentConfig(config) {
|
|
|
399
400
|
providerId: config.agentId,
|
|
400
401
|
timeoutMs: config.timeoutMs ?? AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS,
|
|
401
402
|
kitCommand: config.kitCommand ?? DEFAULT_AGENT_SUCCESS_KIT_COMMAND,
|
|
402
|
-
repairAttempts: config.repairAttempts ?? 0
|
|
403
|
+
repairAttempts: config.repairAttempts ?? 0,
|
|
404
|
+
promptContract: resolveAgentSuccessPromptContract(config)
|
|
403
405
|
};
|
|
404
406
|
}
|
|
405
407
|
/** Captures hidden values for the leak guard, then returns a copy of the task with the reference fixture removed. */
|