@dailephd/my-dev-kit-lab 0.9.1 → 0.9.3
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 +19 -2
- package/README.md +10 -2
- 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 +51 -5
- package/dist/src/core/processTree.js +41 -17
- package/dist/src/core/runMeasuredCommand.js +14 -8
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +24 -2
- 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/campaignProgress.js +4 -2
- 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/writeAgentSuccessCampaignProgressReport.js +6 -2
- package/dist/src/report/experiments/writePluginExperimentReports.js +4 -2
- package/dist/src/securityValidation/attackScenarios/payloadCorpus.js +0 -0
- 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 +34 -4
- package/dist/src/securityValidation/packageChecks/requiredPackageContents.js +106 -2
- package/dist/src/securityValidation/packageChecks/runPackageChecks.js +31 -16
- package/dist/src/securityValidation/staticScans/semgrep.js +68 -64
- package/dist/src/securityValidation/types.js +0 -4
- package/dist/src/securityValidation/validate/runSecurityValidation.js +1 -1
- package/docs/METRICS.md +12 -2
- package/examples/tutorial-browser/prepare.mjs +13 -13
- package/examples/tutorial-browser/server.mjs +13 -13
- package/package.json +1 -1
|
@@ -49,6 +49,7 @@ export async function runMeasuredCommand(options) {
|
|
|
49
49
|
let timedOut = false;
|
|
50
50
|
let timeout;
|
|
51
51
|
let killGraceTimeout;
|
|
52
|
+
let terminationPromise;
|
|
52
53
|
let settled = false;
|
|
53
54
|
let child;
|
|
54
55
|
try {
|
|
@@ -99,7 +100,7 @@ export async function runMeasuredCommand(options) {
|
|
|
99
100
|
const KILL_GRACE_MS = 3000;
|
|
100
101
|
const terminate = () => {
|
|
101
102
|
if (options.terminateProcessTree === true) {
|
|
102
|
-
|
|
103
|
+
terminationPromise ??= terminateProcessTree(child.pid);
|
|
103
104
|
}
|
|
104
105
|
else {
|
|
105
106
|
forceTerminateProcess(child.pid);
|
|
@@ -145,14 +146,19 @@ export async function runMeasuredCommand(options) {
|
|
|
145
146
|
child.stdin?.write(options.stdinText, "utf8");
|
|
146
147
|
child.stdin?.end();
|
|
147
148
|
}
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
// intermediate shim (for example a Windows .cmd launcher) can occasionally outlive an
|
|
152
|
-
// already-killed intermediate process. Without a bounded fallback, a caller whose kill silently
|
|
153
|
-
// failed would await this promise forever instead of receiving the already-known timeout error.
|
|
154
|
-
// (KILL_GRACE_MS and the shared `terminate` step are declared above, beside the output collectors.)
|
|
149
|
+
// Tree termination is awaited before a bounded result is returned. The helper has its own
|
|
150
|
+
// platform-specific time bound; a failed or timed-out tree kill remains visible in `error`.
|
|
151
|
+
// Non-tree callers retain the legacy kill-grace fallback below.
|
|
155
152
|
const settle = async (exitCode) => {
|
|
153
|
+
if (settled)
|
|
154
|
+
return;
|
|
155
|
+
if (terminationPromise) {
|
|
156
|
+
const terminated = await terminationPromise;
|
|
157
|
+
if (!terminated) {
|
|
158
|
+
const cleanupError = "Could not confirm termination of the command process tree.";
|
|
159
|
+
spawnError = spawnError ? `${spawnError} ${cleanupError}` : cleanupError;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
156
162
|
if (settled)
|
|
157
163
|
return;
|
|
158
164
|
settled = true;
|
|
@@ -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";
|
|
@@ -46,7 +47,7 @@ async function plainAncestors(candidate) {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
/** Resolve filesystem aliases before inspecting ancestors (macOS exposes /var as /private/var). */
|
|
49
|
-
async function resolveCampaignRoot(candidate) {
|
|
50
|
+
export async function resolveCampaignRoot(candidate) {
|
|
50
51
|
const resolved = path.resolve(candidate);
|
|
51
52
|
let existing = resolved;
|
|
52
53
|
const remainder = [];
|
|
@@ -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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { lstat, readFile
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { isSameOrInside, resolvePhysicalPath } from "../../../evaluation/benchmarkSandbox/pathPolicy.js";
|
|
3
4
|
import { normalizeAgentDurationV091 as normalizedDuration, normalizeAgentTokenUsageV091 as normalizedTokens } from "../../../agents/normalizeAgentTelemetry.js";
|
|
4
5
|
import { validateAgentSuccessRateExecutionArtifact } from "./executionArtifact.js";
|
|
5
6
|
import { validateAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateArtifactFamily } from "./analysisArtifact.js";
|
|
@@ -88,7 +89,8 @@ export async function readCampaignProgressScientificFamily(snapshot, boundary) {
|
|
|
88
89
|
const load = async (name) => {
|
|
89
90
|
const ref = cp.finalization.artifacts.find(a => a.path === name);
|
|
90
91
|
const file = path.join(boundary.outputRoot, name), stat = await lstat(file);
|
|
91
|
-
|
|
92
|
+
const [physicalRoot, physicalFile] = await Promise.all([resolvePhysicalPath(boundary.outputRoot), resolvePhysicalPath(file)]);
|
|
93
|
+
campaignAssert(stat.isFile() && !stat.isSymbolicLink() && stat.size === ref.bytes && isSameOrInside(physicalRoot, physicalFile) && path.dirname(physicalFile) === physicalRoot, "SCIENTIFIC_ARTIFACT_UNSAFE");
|
|
92
94
|
const bytes = await readFile(file);
|
|
93
95
|
campaignAssert(bytes.length === ref.bytes && campaignHash(bytes) === ref.sha256, "SCIENTIFIC_ARTIFACT_CHANGED");
|
|
94
96
|
return JSON.parse(bytes.toString("utf8"));
|
|
@@ -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. */
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AGENT_SUCCESS_PROMPT_EXPLANATION_MODES, AGENT_SUCCESS_PROMPT_LENGTH_MODES, AGENT_SUCCESS_PROMPT_TEMPLATE_IDS, AGENT_SUCCESS_PROMPT_TOOL_USE_MODES, REAL_AGENT_CONTEXT_BEGIN, REAL_AGENT_CONTEXT_END, describeAgentSuccessPromptInstructions, isLegacyDefaultPromptSettings } from "./realAgentPrompt.js";
|
|
3
|
+
/**
|
|
4
|
+
* PromptContractV1 is the typed, persisted identity of the effective patch-generation prompt for one provider. It is
|
|
5
|
+
* deliberately separate from the context-treatment identity (raw-full-file / context-pack): that identity names the
|
|
6
|
+
* context transport, this one names the instruction template. It never carries source/context bodies, trusted checks
|
|
7
|
+
* or provider output.
|
|
8
|
+
*/
|
|
9
|
+
export const PROMPT_CONTRACT_SCHEMA_VERSION = "1.0.0";
|
|
10
|
+
export const PROMPT_CONTRACT_ARTIFACT_FILE = "prompt-contract.json";
|
|
11
|
+
const CONTRACT_FIELDS = ["schemaVersion", "templateId", "providerId", "lengthMode", "explanationMode", "toolUseMode", "maxCommandCount", "rendererDigest"];
|
|
12
|
+
function sha256(value) {
|
|
13
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
14
|
+
}
|
|
15
|
+
/** Sorted-key canonical JSON for flat, finite, string/number/array/object data (the only shapes this module hashes). */
|
|
16
|
+
function canonicalJson(value) {
|
|
17
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string")
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
if (Array.isArray(value))
|
|
20
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
21
|
+
const record = value;
|
|
22
|
+
return `{${Object.keys(record)
|
|
23
|
+
.sort()
|
|
24
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
|
25
|
+
.join(",")}}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Digest of the normalized, versioned template definition: the effective settings plus the exact instruction lines and
|
|
29
|
+
* context delimiters the renderer emits for them. It never reads task data, the answer key or hidden checks.
|
|
30
|
+
*/
|
|
31
|
+
export function computePromptRendererDigest(settings) {
|
|
32
|
+
return sha256(canonicalJson({
|
|
33
|
+
definition: "my-dev-kit-lab-agent-success-rate-prompt-template-v1",
|
|
34
|
+
settings: {
|
|
35
|
+
templateId: settings.templateId,
|
|
36
|
+
providerId: settings.providerId,
|
|
37
|
+
lengthMode: settings.lengthMode,
|
|
38
|
+
explanationMode: settings.explanationMode,
|
|
39
|
+
toolUseMode: settings.toolUseMode,
|
|
40
|
+
maxCommandCount: settings.maxCommandCount
|
|
41
|
+
},
|
|
42
|
+
delimiters: [REAL_AGENT_CONTEXT_BEGIN, REAL_AGENT_CONTEXT_END],
|
|
43
|
+
instructions: describeAgentSuccessPromptInstructions(settings)
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
/** Normalizes a selection (omitted fields take the inherited defaults) into a complete contract. */
|
|
47
|
+
export function createPromptContract(providerId, selection = {}) {
|
|
48
|
+
const settings = {
|
|
49
|
+
templateId: selection.templateId ?? "legacy-diff-v1",
|
|
50
|
+
providerId,
|
|
51
|
+
lengthMode: selection.lengthMode ?? "standard",
|
|
52
|
+
explanationMode: selection.explanationMode ?? "legacy",
|
|
53
|
+
toolUseMode: selection.toolUseMode ?? "prohibited",
|
|
54
|
+
maxCommandCount: selection.maxCommandCount ?? 0
|
|
55
|
+
};
|
|
56
|
+
const contract = {
|
|
57
|
+
schemaVersion: PROMPT_CONTRACT_SCHEMA_VERSION,
|
|
58
|
+
templateId: settings.templateId,
|
|
59
|
+
providerId,
|
|
60
|
+
lengthMode: settings.lengthMode,
|
|
61
|
+
explanationMode: settings.explanationMode,
|
|
62
|
+
toolUseMode: settings.toolUseMode,
|
|
63
|
+
maxCommandCount: 0,
|
|
64
|
+
rendererDigest: computePromptRendererDigest(settings)
|
|
65
|
+
};
|
|
66
|
+
assertPromptContractShape(contract);
|
|
67
|
+
return contract;
|
|
68
|
+
}
|
|
69
|
+
/** Throws on unknown fields, missing fields or out-of-vocabulary values. Does not recompute the renderer digest. */
|
|
70
|
+
export function assertPromptContractShape(value) {
|
|
71
|
+
const fail = (reason) => {
|
|
72
|
+
throw new Error(`INVALID_PROMPT_CONTRACT: ${reason}`);
|
|
73
|
+
};
|
|
74
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
75
|
+
fail("not an object");
|
|
76
|
+
const record = value;
|
|
77
|
+
const keys = Object.keys(record).sort();
|
|
78
|
+
if (keys.join("\0") !== [...CONTRACT_FIELDS].sort().join("\0"))
|
|
79
|
+
fail("fields are not exactly the PromptContractV1 fields");
|
|
80
|
+
if (record.schemaVersion !== PROMPT_CONTRACT_SCHEMA_VERSION)
|
|
81
|
+
fail("unsupported schemaVersion");
|
|
82
|
+
if (!AGENT_SUCCESS_PROMPT_TEMPLATE_IDS.includes(record.templateId))
|
|
83
|
+
fail("unknown templateId");
|
|
84
|
+
if (record.providerId !== "codex" && record.providerId !== "claude")
|
|
85
|
+
fail("unknown providerId");
|
|
86
|
+
if (!AGENT_SUCCESS_PROMPT_LENGTH_MODES.includes(record.lengthMode))
|
|
87
|
+
fail("unknown lengthMode");
|
|
88
|
+
if (!AGENT_SUCCESS_PROMPT_EXPLANATION_MODES.includes(record.explanationMode))
|
|
89
|
+
fail("unknown explanationMode");
|
|
90
|
+
if (!AGENT_SUCCESS_PROMPT_TOOL_USE_MODES.includes(record.toolUseMode))
|
|
91
|
+
fail("unknown toolUseMode");
|
|
92
|
+
if (record.maxCommandCount !== 0)
|
|
93
|
+
fail("maxCommandCount must be 0");
|
|
94
|
+
if (typeof record.rendererDigest !== "string" || !/^[a-f0-9]{64}$/.test(record.rendererDigest))
|
|
95
|
+
fail("invalid rendererDigest");
|
|
96
|
+
}
|
|
97
|
+
/** True when the contract's renderer digest equals the digest the current renderer produces for its settings. */
|
|
98
|
+
export function promptContractMatchesCurrentRenderer(contract) {
|
|
99
|
+
return contract.rendererDigest === computePromptRendererDigest(promptSettingsOf(contract));
|
|
100
|
+
}
|
|
101
|
+
export function promptSettingsOf(contract) {
|
|
102
|
+
return {
|
|
103
|
+
templateId: contract.templateId,
|
|
104
|
+
providerId: contract.providerId,
|
|
105
|
+
lengthMode: contract.lengthMode,
|
|
106
|
+
explanationMode: contract.explanationMode,
|
|
107
|
+
toolUseMode: contract.toolUseMode,
|
|
108
|
+
maxCommandCount: contract.maxCommandCount
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function promptContractDigest(contract) {
|
|
112
|
+
assertPromptContractShape(contract);
|
|
113
|
+
return sha256(canonicalJson(contract));
|
|
114
|
+
}
|
|
115
|
+
export function isLegacyDefaultPromptContract(contract) {
|
|
116
|
+
return isLegacyDefaultPromptSettings(contract);
|
|
117
|
+
}
|
|
118
|
+
/** Exact bytes of the per-attempt prompt-contract.json artifact; deterministic, so recovery can recompute and compare. */
|
|
119
|
+
export function renderPromptContractArtifact(contract) {
|
|
120
|
+
return `${canonicalJson({ promptContract: contract, promptContractDigest: promptContractDigest(contract) })}\n`;
|
|
121
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtemp, readdir, rm } from "node:fs/promises";
|
|
1
|
+
import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { normalizeAgentDurationV091, normalizeAgentTokenUsageV091 } from "../../../agents/index.js";
|
|
@@ -9,6 +9,8 @@ import { sanitizePathSegment } from "../../outputPaths.js";
|
|
|
9
9
|
import { findPromptLeaks } from "./contextGeneration.js";
|
|
10
10
|
import { boundMessage } from "./execution.js";
|
|
11
11
|
import { buildAgentSuccessRealAgentPrompt } from "./realAgentPrompt.js";
|
|
12
|
+
import { PROMPT_CONTRACT_ARTIFACT_FILE, createPromptContract, promptSettingsOf, renderPromptContractArtifact } from "./promptContract.js";
|
|
13
|
+
import { validateAgentSuccessFinalAnswer } from "./strictDiffResponse.js";
|
|
12
14
|
import { summarizeAgentSuccessRepairFeedback } from "./repairFeedback.js";
|
|
13
15
|
export const AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS = 240_000;
|
|
14
16
|
export const AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS = 1_800_000;
|
|
@@ -109,6 +111,9 @@ async function assertNeutralCwd(cwd, protectedRoots) {
|
|
|
109
111
|
*/
|
|
110
112
|
export function createRealAgentProposalSource(args) {
|
|
111
113
|
const { settings } = args;
|
|
114
|
+
const promptContract = settings.promptContract ?? createPromptContract(settings.providerId);
|
|
115
|
+
if (promptContract.providerId !== settings.providerId)
|
|
116
|
+
throw new Error("PROMPT_CONTRACT_PROVIDER_MISMATCH");
|
|
112
117
|
const attemptNumber = args.attemptNumber ?? 1;
|
|
113
118
|
return async ({ treatmentId, invoke, notInvokedReason }) => {
|
|
114
119
|
let runtimeTelemetry = projectAgentSuccessProviderTelemetryV091(null, "not-invoked", null);
|
|
@@ -165,6 +170,7 @@ export function createRealAgentProposalSource(args) {
|
|
|
165
170
|
task,
|
|
166
171
|
treatmentId,
|
|
167
172
|
contextText: context.text,
|
|
173
|
+
prompt: promptSettingsOf(promptContract),
|
|
168
174
|
...(args.repair ? { repair: { feedback: args.repair.feedback, attemptNumber, maxAttempts: args.repair.maxAttempts } } : {})
|
|
169
175
|
});
|
|
170
176
|
const leaks = findPromptLeaks(promptText, args.forbidden);
|
|
@@ -256,6 +262,14 @@ export function createRealAgentProposalSource(args) {
|
|
|
256
262
|
const agentDurationMs = Number.isFinite(result.durationMs) && result.durationMs >= 0 ? result.durationMs : Date.now() - wallStarted;
|
|
257
263
|
const agentTokenUsage = tokenEvidence(result);
|
|
258
264
|
runtimeTelemetry = projectAgentSuccessProviderTelemetryV091(result, providerStatus, agentDurationMs);
|
|
265
|
+
// The effective contract is persisted next to the provider artifacts so recovery can prove which prompt produced them.
|
|
266
|
+
try {
|
|
267
|
+
await mkdir(attemptAbsolute, { recursive: true });
|
|
268
|
+
await writeFile(path.join(attemptAbsolute, PROMPT_CONTRACT_ARTIFACT_FILE), renderPromptContractArtifact(promptContract), { encoding: "utf8", flag: "wx" });
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return noProposal("prompt-contract-not-persisted", "PROMPT_CONTRACT_NOT_PERSISTED", "The effective prompt contract could not be persisted; the provider answer was not evaluated.");
|
|
272
|
+
}
|
|
259
273
|
if (providerStatus !== "completed") {
|
|
260
274
|
return noProposal(classification.statusReason, PROVIDER_STATUS_CODES[providerStatus] ?? "PROVIDER_FAILED", `Provider ${settings.providerId} ended with status ${providerStatus}: ${classification.statusReason}`, {
|
|
261
275
|
agentDurationMs,
|
|
@@ -267,6 +281,22 @@ export function createRealAgentProposalSource(args) {
|
|
|
267
281
|
}
|
|
268
282
|
realAgent.finalAnswerAvailable = true;
|
|
269
283
|
realAgent.providerStatusReason = null;
|
|
270
|
-
|
|
284
|
+
// Application-level output contract, applied only to a completed provider's extracted final answer. An invalid
|
|
285
|
+
// response is handed to the patch pipeline as an empty proposal: it is recorded as malformed, never re-interpreted
|
|
286
|
+
// through legacy extraction, and the diagnostic reason code is retained as a bounded error.
|
|
287
|
+
const output = validateAgentSuccessFinalAnswer(promptContract, result.finalAnswerText);
|
|
288
|
+
if (!output.ok) {
|
|
289
|
+
return {
|
|
290
|
+
kind: "proposal",
|
|
291
|
+
...runtimeTelemetry,
|
|
292
|
+
text: "",
|
|
293
|
+
realAgent,
|
|
294
|
+
agentDurationMs,
|
|
295
|
+
agentTokenUsage,
|
|
296
|
+
errors: [{ code: `OUTPUT_CONTRACT_${output.code}`, message: `The provider final answer violated the ${promptContract.templateId} output contract (${output.code}).` }],
|
|
297
|
+
contextFile
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
return { kind: "proposal", ...runtimeTelemetry, text: output.patchText, realAgent, agentDurationMs, agentTokenUsage, errors: [], contextFile };
|
|
271
301
|
};
|
|
272
302
|
}
|