@dailephd/my-dev-kit-lab 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +19 -3
  3. package/benchmarks/contracts/agent-success-rate-project-profiles.json +33 -0
  4. package/benchmarks/contracts/agent-success-rate-tasks.json +671 -0
  5. package/benchmarks/projects/agent-success-inventory-node/.gitattributes +2 -0
  6. package/benchmarks/projects/agent-success-inventory-node/README.md +74 -0
  7. package/benchmarks/projects/agent-success-inventory-node/package.json +14 -0
  8. package/benchmarks/projects/agent-success-inventory-node/src/fulfillmentReport.js +38 -0
  9. package/benchmarks/projects/agent-success-inventory-node/src/index.js +26 -0
  10. package/benchmarks/projects/agent-success-inventory-node/src/inventoryStore.js +114 -0
  11. package/benchmarks/projects/agent-success-inventory-node/src/quantity.js +19 -0
  12. package/benchmarks/projects/agent-success-inventory-node/src/reservationService.js +45 -0
  13. package/benchmarks/projects/agent-success-inventory-node/tests/quantity-edge.check.mjs +51 -0
  14. package/benchmarks/projects/agent-success-inventory-node/tests/quantity-primary.check.mjs +46 -0
  15. package/benchmarks/projects/agent-success-inventory-node/tests/regression.check.mjs +115 -0
  16. package/benchmarks/projects/agent-success-inventory-node/tests/report-edge.check.mjs +109 -0
  17. package/benchmarks/projects/agent-success-inventory-node/tests/report-primary.check.mjs +47 -0
  18. package/benchmarks/projects/agent-success-inventory-node/tests/reservation-edge.check.mjs +63 -0
  19. package/benchmarks/projects/agent-success-inventory-node/tests/reservation-primary.check.mjs +44 -0
  20. package/benchmarks/projects/agent-success-task-board-node/.gitattributes +2 -0
  21. package/benchmarks/projects/agent-success-task-board-node/README.md +77 -0
  22. package/benchmarks/projects/agent-success-task-board-node/package.json +14 -0
  23. package/benchmarks/projects/agent-success-task-board-node/src/index.js +24 -0
  24. package/benchmarks/projects/agent-success-task-board-node/src/projectSummary.js +15 -0
  25. package/benchmarks/projects/agent-success-task-board-node/src/taskService.js +58 -0
  26. package/benchmarks/projects/agent-success-task-board-node/src/taskStore.js +58 -0
  27. package/benchmarks/projects/agent-success-task-board-node/src/validation.js +31 -0
  28. package/benchmarks/projects/agent-success-task-board-node/tests/import-edge.check.mjs +65 -0
  29. package/benchmarks/projects/agent-success-task-board-node/tests/import-primary.check.mjs +31 -0
  30. package/benchmarks/projects/agent-success-task-board-node/tests/regression.check.mjs +107 -0
  31. package/benchmarks/projects/agent-success-task-board-node/tests/summary-edge.check.mjs +67 -0
  32. package/benchmarks/projects/agent-success-task-board-node/tests/summary-primary.check.mjs +54 -0
  33. package/benchmarks/projects/agent-success-task-board-node/tests/title-edge.check.mjs +39 -0
  34. package/benchmarks/projects/agent-success-task-board-node/tests/title-primary.check.mjs +29 -0
  35. package/dist/src/agents/adapters/claudeAdapter.js +6 -3
  36. package/dist/src/agents/adapters/codexAdapter.js +51 -10
  37. package/dist/src/agents/index.js +1 -0
  38. package/dist/src/agents/normalizeAgentTelemetry.js +50 -0
  39. package/dist/src/agents/parseAgentTokenUsage.js +71 -49
  40. package/dist/src/agents/providerBuildObservation.js +97 -0
  41. package/dist/src/audits/core/projectInventory.js +1 -0
  42. package/dist/src/cli/help.js +44 -1
  43. package/dist/src/commands/runExperimentDescribeCommand.js +83 -1
  44. package/dist/src/commands/runExperimentRunCommand.js +309 -6
  45. package/dist/src/core/processTree.js +55 -0
  46. package/dist/src/core/runMeasuredCommand.js +108 -22
  47. package/dist/src/evaluation/agentSuccess/applyPatchToSandbox.js +37 -0
  48. package/dist/src/evaluation/agentSuccess/assessBaseline.js +23 -0
  49. package/dist/src/evaluation/agentSuccess/corpusTypes.js +74 -0
  50. package/dist/src/evaluation/agentSuccess/extractPatchCandidate.js +46 -0
  51. package/dist/src/evaluation/agentSuccess/index.js +14 -0
  52. package/dist/src/evaluation/agentSuccess/parseUnifiedDiff.js +210 -0
  53. package/dist/src/evaluation/agentSuccess/patchTypes.js +5 -0
  54. package/dist/src/evaluation/agentSuccess/readAgentSuccessCorpus.js +183 -0
  55. package/dist/src/evaluation/agentSuccess/runVerificationCheck.js +100 -0
  56. package/dist/src/evaluation/agentSuccess/taskPaths.js +34 -0
  57. package/dist/src/evaluation/agentSuccess/taskTypes.js +14 -0
  58. package/dist/src/evaluation/agentSuccess/validateAgentSuccessCorpus.js +307 -0
  59. package/dist/src/evaluation/agentSuccess/validateAgentSuccessTask.js +297 -0
  60. package/dist/src/evaluation/agentSuccess/validatePatchPolicy.js +66 -0
  61. package/dist/src/evaluation/agentSuccess/verificationTypes.js +1 -0
  62. package/dist/src/evaluation/benchmarkSandbox/createBenchmarkSandbox.js +127 -0
  63. package/dist/src/evaluation/benchmarkSandbox/errors.js +11 -0
  64. package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +62 -0
  65. package/dist/src/evaluation/benchmarkSandbox/index.js +8 -0
  66. package/dist/src/evaluation/benchmarkSandbox/minimalHostEnv.js +37 -0
  67. package/dist/src/evaluation/benchmarkSandbox/pathPolicy.js +49 -0
  68. package/dist/src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.js +74 -0
  69. package/dist/src/evaluation/benchmarkSandbox/treeSnapshot.js +57 -0
  70. package/dist/src/evaluation/benchmarkSandbox/types.js +3 -0
  71. package/dist/src/evaluation/changeSet/captureChangeSet.js +98 -0
  72. package/dist/src/evaluation/changeSet/index.js +2 -0
  73. package/dist/src/evaluation/changeSet/types.js +9 -0
  74. package/dist/src/experiments/defaultRegistry.js +2 -0
  75. package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
  76. package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
  77. package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
  78. package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
  79. package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
  80. package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpoint.js +216 -0
  81. package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +588 -0
  82. package/dist/src/experiments/plugins/agentSuccessRate/campaignEvidenceQuality.js +77 -0
  83. package/dist/src/experiments/plugins/agentSuccessRate/campaignExecution.js +314 -0
  84. package/dist/src/experiments/plugins/agentSuccessRate/campaignIdentity.js +111 -0
  85. package/dist/src/experiments/plugins/agentSuccessRate/campaignProgress.js +173 -0
  86. package/dist/src/experiments/plugins/agentSuccessRate/campaignQueue.js +11 -0
  87. package/dist/src/experiments/plugins/agentSuccessRate/campaignRecovery.js +256 -0
  88. package/dist/src/experiments/plugins/agentSuccessRate/campaignResumableExecution.js +207 -0
  89. package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
  90. package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
  91. package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
  92. package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
  93. package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
  94. package/dist/src/experiments/plugins/agentSuccessRate/index.js +26 -0
  95. package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
  96. package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
  97. package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +517 -0
  98. package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +272 -0
  99. package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
  100. package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
  101. package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
  102. package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
  103. package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
  104. package/dist/src/experiments/plugins/agentSuccessRate/timeoutPresets.js +9 -0
  105. package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
  106. package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
  107. package/dist/src/report/experiments/agentSuccessCampaignProgressReportModel.js +1 -0
  108. package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
  109. package/dist/src/report/experiments/buildAgentSuccessCampaignProgressReport.js +6 -0
  110. package/dist/src/report/experiments/buildAgentSuccessRateReport.js +266 -0
  111. package/dist/src/report/experiments/buildPluginExperimentReport.js +27 -2
  112. package/dist/src/report/experiments/index.js +4 -0
  113. package/dist/src/report/experiments/renderAgentSuccessCampaignProgressHtml.js +6 -0
  114. package/dist/src/report/experiments/renderAgentSuccessCampaignProgressText.js +25 -0
  115. package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +171 -0
  116. package/dist/src/report/experiments/renderAgentSuccessRateText.js +146 -0
  117. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  118. package/dist/src/report/experiments/renderPluginExperimentReportText.js +5 -0
  119. package/dist/src/report/experiments/writeAgentSuccessCampaignProgressReport.js +135 -0
  120. package/dist/src/report/experiments/writePluginExperimentReports.js +4 -3
  121. package/docs/METRICS.md +79 -0
  122. package/package.json +1 -1
@@ -0,0 +1,11 @@
1
+ import { validateAgentSuccessTask } from "../../../evaluation/agentSuccess/index.js";
2
+ import { campaignAssert, canonicalCampaignJson } from "./campaignIdentity.js";
3
+ import { AGENT_SUCCESS_RATE_TREATMENT_IDS } from "./metadata.js";
4
+ /** Selection is already established by selectAgentSuccessTasks. Never sort, interleave or drop a treatment. */
5
+ export function buildCampaignQueue(tasks, treatments, limit) {
6
+ campaignAssert(tasks.length > 0 && tasks.length <= 1000 && new Set(tasks.map((t) => t.id)).size === tasks.length, "INVALID_SELECTION");
7
+ tasks.forEach((task) => campaignAssert(validateAgentSuccessTask(task).ok, "INVALID_SELECTION"));
8
+ campaignAssert(canonicalCampaignJson(treatments) === canonicalCampaignJson(AGENT_SUCCESS_RATE_TREATMENT_IDS), "FIXED_TREATMENTS_REQUIRED");
9
+ campaignAssert(limit === "one-case" || limit === "all-selected-cases", "INVALID_EXECUTION_LIMIT");
10
+ return tasks.slice(0, limit === "one-case" ? 1 : tasks.length).flatMap((task, caseIndex) => AGENT_SUCCESS_RATE_TREATMENT_IDS.map((treatmentId, treatmentIndex) => ({ task, treatmentId, checkpointIndex: caseIndex * 2 + treatmentIndex })));
11
+ }
@@ -0,0 +1,256 @@
1
+ import { campaignAssert, campaignExact, campaignHash, campaignIdentifier, canonicalCampaignJson, isCampaignDigest, validateCampaignIdentity } from "./campaignIdentity.js";
2
+ import { CAMPAIGN_CHECKPOINT_MAX_BYTES, createCampaignCheckpoint, projectCampaignTreatmentEvidence, requiredCampaignArtifacts, validateCampaignCompletion, validateCampaignRelativePath } from "./campaignCheckpoint.js";
3
+ import { analyzeAgentSuccessTreatment } from "./analysis.js";
4
+ import { classifyAgentSuccessAttempt } from "./repairPolicy.js";
5
+ export const CAMPAIGN_CHECKPOINT_SCHEMA_V2 = "my-dev-kit-lab-agent-success-rate-campaign-checkpoint-v2";
6
+ export const CAMPAIGN_EPOCH_SCHEMA = "campaign-invocation-epoch-v1";
7
+ const equal = (a, b) => canonicalCampaignJson(a) === canonicalCampaignJson(b);
8
+ function timestamp(value) {
9
+ campaignAssert(typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) && new Date(value).toISOString() === value, "INVALID_TIMESTAMP");
10
+ }
11
+ function generation(value) { campaignAssert(Number.isSafeInteger(value) && Number(value) >= 1 && Number(value) <= 1_000_000, "INVALID_GENERATION"); }
12
+ export function campaignEpochNamespace(caseId, treatmentId, ordinal, id) {
13
+ campaignAssert(campaignIdentifier(caseId) && ["raw-full-file", "context-pack"].includes(treatmentId) && [1, 2].includes(ordinal) && /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(id), "INVALID_EPOCH_ID");
14
+ // Compact, deterministic case/treatment labels leave room for Git's internal paths on Windows.
15
+ // The full epoch UUID is retained (without separators); the marker also binds the full case identity.
16
+ const relative = `campaign-epochs-v2/${campaignHash(caseId).slice(0, 12)}/${treatmentId === "raw-full-file" ? "raw" : "pack"}/${ordinal}-${id.replace(/-/g, "")}`;
17
+ validateCampaignRelativePath(relative);
18
+ return relative;
19
+ }
20
+ /** Only artifact references change. Source/check/protected paths and scientific evidence remain untouched. */
21
+ export function mapCampaignEpochEvidence(source, namespace, direction) {
22
+ validateCampaignRelativePath(namespace);
23
+ const evidence = structuredClone(source);
24
+ const map = (value) => {
25
+ if (value === null)
26
+ return null;
27
+ validateCampaignRelativePath(value);
28
+ if (direction === "prefix")
29
+ return `${namespace}/${value}`;
30
+ campaignAssert(value.startsWith(`${namespace}/`), "EPOCH_ARTIFACT_SCOPE");
31
+ const relative = value.slice(namespace.length + 1);
32
+ validateCampaignRelativePath(relative);
33
+ return relative;
34
+ };
35
+ const apply = (e) => {
36
+ e.proposedPatchPath = map(e.proposedPatchPath);
37
+ e.appliedPatchPath = map(e.appliedPatchPath);
38
+ if (e.realAgent) {
39
+ e.realAgent.context.contextArtifactPath = map(e.realAgent.context.contextArtifactPath);
40
+ e.realAgent.agentArtifactDirectory = map(e.realAgent.agentArtifactDirectory);
41
+ for (const key of Object.keys(e.realAgent.agentArtifacts))
42
+ e.realAgent.agentArtifacts[key] = map(e.realAgent.agentArtifacts[key]);
43
+ }
44
+ };
45
+ apply(evidence);
46
+ evidence.attempts?.forEach(a => apply(a.evidence));
47
+ return evidence;
48
+ }
49
+ export function validateCampaignEpochArtifacts(value, expected) {
50
+ campaignAssert(Array.isArray(value) && value.length <= 32, "ARTIFACT_REFERENCE_MISMATCH");
51
+ value.forEach(r => { campaignExact(r, ["path", "kind", "sha256", "bytes"]); validateCampaignRelativePath(r.path); campaignAssert(["patch", "context", "provider"].includes(String(r.kind)) && isCampaignDigest(r.sha256) && Number.isSafeInteger(r.bytes) && Number(r.bytes) >= 0 && Number(r.bytes) <= 32 * 1024 * 1024); });
52
+ campaignAssert(equal(value.map(({ path, kind }) => ({ path, kind })), expected), "ARTIFACT_REFERENCE_MISMATCH");
53
+ }
54
+ export function validateCampaignLimitAuthorization(value, cp, unit) {
55
+ campaignExact(value, ["identityDigest", "generation", "revision", "caseId", "treatmentId", "acknowledgeInvocation", "acknowledgeExternalLimit"]);
56
+ campaignAssert(value.identityDigest === cp.identity.digest && value.generation === cp.generation && value.revision === cp.integrity && value.caseId === unit.caseId && value.treatmentId === unit.treatmentId && value.acknowledgeInvocation === true && value.acknowledgeExternalLimit === true, "LIMIT_AUTHORIZATION_MISMATCH");
57
+ const previous = unit.epochs.at(-1);
58
+ campaignAssert(unit.state === "paused-limit" && unit.epochs.length === 1 && previous?.phase === "terminal" && previous.terminal?.reason === "provider-limit" && unit.completion === null, "LIMIT_CONTINUATION_NOT_ELIGIBLE");
59
+ }
60
+ export function sealCampaignCheckpointV2(body) { return { ...body, integrity: campaignHash(canonicalCampaignJson(body)) }; }
61
+ export function createCampaignCheckpointV2(identity, tasks, runId, at) {
62
+ const old = createCampaignCheckpoint(identity, tasks, at);
63
+ const cp = sealCampaignCheckpointV2({ schemaVersion: CAMPAIGN_CHECKPOINT_SCHEMA_V2, identity: structuredClone(identity), runId, generation: 1, parentIntegrity: null, createdAt: at, updatedAt: at, units: old.units.map(u => ({ caseId: u.caseId, treatmentId: u.treatmentId, state: "pending", epochs: [], completion: null })), finalization: null });
64
+ validateCampaignCheckpointV2(cp, tasks);
65
+ return cp;
66
+ }
67
+ export function validateCampaignCheckpointV2(value, tasks) {
68
+ campaignAssert(Buffer.byteLength(canonicalCampaignJson(value)) <= CAMPAIGN_CHECKPOINT_MAX_BYTES, "DATA_BOUND");
69
+ campaignExact(value, ["schemaVersion", "identity", "runId", "generation", "parentIntegrity", "createdAt", "updatedAt", "units", "finalization", "integrity"]);
70
+ campaignAssert(value.schemaVersion === CAMPAIGN_CHECKPOINT_SCHEMA_V2, "UNSUPPORTED_SCHEMA");
71
+ validateCampaignIdentity(value.identity);
72
+ generation(value.generation);
73
+ campaignAssert(campaignIdentifier(value.runId));
74
+ timestamp(value.createdAt);
75
+ timestamp(value.updatedAt);
76
+ campaignAssert(value.updatedAt >= value.createdAt && (value.parentIntegrity === null || isCampaignDigest(value.parentIntegrity)));
77
+ const identity = value.identity;
78
+ const createdAt = value.createdAt;
79
+ const updatedAt = value.updatedAt;
80
+ campaignAssert(tasks.length === identity.selected.length && tasks.every((t, i) => t.id === identity.selected[i].caseId && t.benchmarkProject === identity.selected[i].projectId && campaignHash(canonicalCampaignJson(t)) === identity.selected[i].taskDigest), "TASK_IDENTITY_MISMATCH");
81
+ campaignAssert(Array.isArray(value.units) && value.units.length === tasks.length * 2);
82
+ const ids = new Set();
83
+ value.units.forEach((u, index) => {
84
+ campaignExact(u, ["caseId", "treatmentId", "state", "epochs", "completion"]);
85
+ const task = tasks[Math.floor(index / 2)];
86
+ campaignAssert(u.caseId === task.id && u.treatmentId === identity.treatmentOrder[index % 2] && Array.isArray(u.epochs) && u.epochs.length <= 2, "UNIT_IDENTITY");
87
+ campaignAssert(["pending", "in-progress", "completed", "paused-limit", "incomplete", "uncertain"].includes(String(u.state)));
88
+ const epochs = u.epochs;
89
+ epochs.forEach((e, i) => {
90
+ campaignExact(e, ["schemaVersion", "caseId", "treatmentId", "ordinal", "id", "previousEpochId", "parentRevision", "phase", "configuration", "namespace", "authorization", "reservedAt", "authorizedAt", "terminalAt", "terminal"]);
91
+ campaignAssert(e.schemaVersion === CAMPAIGN_EPOCH_SCHEMA && e.caseId === u.caseId && e.treatmentId === u.treatmentId && e.ordinal === i + 1 && !ids.has(String(e.id)), "EPOCH_IDENTITY");
92
+ ids.add(String(e.id));
93
+ campaignAssert(e.namespace === campaignEpochNamespace(String(u.caseId), String(u.treatmentId), i + 1, String(e.id)) && isCampaignDigest(e.parentRevision) && equal(e.configuration, identity.config), "EPOCH_CONFIGURATION");
94
+ timestamp(e.reservedAt);
95
+ campaignAssert(e.reservedAt >= createdAt && e.reservedAt <= updatedAt);
96
+ campaignExact(e.authorization, ["intent", "scope"]);
97
+ if (i === 0)
98
+ campaignAssert(e.previousEpochId === null && e.authorization.intent === "initial" && e.authorization.scope === null);
99
+ else {
100
+ const prior = epochs[i - 1];
101
+ campaignAssert(e.previousEpochId === prior.id && prior.phase === "terminal" && prior.terminal?.reason === "provider-limit" && e.authorization.intent === "limit-continuation", "EPOCH_CONTINUATION");
102
+ const scope = e.authorization.scope;
103
+ campaignExact(scope, ["identityDigest", "generation", "revision", "caseId", "treatmentId", "acknowledgeInvocation", "acknowledgeExternalLimit"]);
104
+ generation(scope.generation);
105
+ campaignAssert(scope.identityDigest === identity.digest && scope.revision === e.parentRevision && scope.caseId === u.caseId && scope.treatmentId === u.treatmentId && scope.acknowledgeInvocation === true && scope.acknowledgeExternalLimit === true && Number(scope.generation) < Number(value.generation));
106
+ }
107
+ campaignAssert(["reserved", "invocation-authorized", "terminal", "uncertain"].includes(String(e.phase)));
108
+ if (e.phase === "reserved")
109
+ campaignAssert(e.authorizedAt === null && e.terminalAt === null && e.terminal === null);
110
+ else {
111
+ timestamp(e.authorizedAt);
112
+ campaignAssert(e.authorizedAt >= e.reservedAt && e.authorizedAt <= updatedAt);
113
+ }
114
+ if (e.phase !== "terminal")
115
+ campaignAssert(e.terminalAt === null && e.terminal === null);
116
+ else {
117
+ timestamp(e.terminalAt);
118
+ campaignAssert(e.terminalAt >= String(e.authorizedAt) && e.terminalAt <= updatedAt);
119
+ const t = e.terminal;
120
+ campaignExact(t, ["outcome", "reason", "evidence", "artifacts"]);
121
+ campaignAssert(["evaluated", "provider-limit", "incomplete"].includes(String(t.reason)));
122
+ const evidence = t.evidence;
123
+ campaignAssert(equal(evidence, projectCampaignTreatmentEvidence(evidence)), "PRIVATE_OR_INVALID_EVIDENCE");
124
+ const local = mapCampaignEpochEvidence(evidence, String(e.namespace), "strip");
125
+ campaignAssert(local.treatmentId === u.treatmentId && t.outcome === (local.realAgent?.providerStatus ?? "not-invoked"), "TERMINAL_OUTCOME");
126
+ const rows = local.attempts?.map(a => a.evidence) ?? [local];
127
+ campaignAssert(rows.length > 0 && rows.length <= 1 + identity.config.repairAttempts, "ATTEMPT_BOUND");
128
+ rows.forEach((a, j) => campaignAssert(a.treatmentId === u.treatmentId && (!a.realAgent || (a.realAgent.providerId === identity.config.providerId && a.realAgent.attempt === j + 1)) && (!local.attempts || local.attempts[j].attemptNumber === j + 1), "ATTEMPT_IDENTITY"));
129
+ if (local.attempts) {
130
+ const { attempts: _attempts, ...final } = local;
131
+ campaignAssert(equal(final, rows.at(-1)), "FINAL_ATTEMPT_MISMATCH");
132
+ local.attempts.forEach((a, j) => {
133
+ const row = rows[j];
134
+ const classification = classifyAgentSuccessAttempt(row, analyzeAgentSuccessTreatment(task, row).metrics);
135
+ campaignAssert(a.providerId === identity.config.providerId && a.providerStatus === row.realAgent?.providerStatus && a.failureCategory === classification.category && a.repairEligible === classification.repairEligible && a.taskSuccess === classification.taskSuccess && equal(a.cleanupResult, row.cleanup) && equal(a.tokenUsage, row.agentTokenUsage) && a.durationMs === row.timing.agentDurationMs && a.completedAt >= a.startedAt, "ATTEMPT_PROOF_MISMATCH");
136
+ if (j > 0)
137
+ campaignAssert(local.attempts[j - 1].repairEligible && local.attempts[j - 1].taskSuccess === false, "REPAIR_NOT_ELIGIBLE");
138
+ });
139
+ }
140
+ campaignAssert(t.reason === "provider-limit" || t.outcome !== "agent-limit-reached", "LIMIT_CLASSIFICATION_REQUIRED");
141
+ const expected = requiredCampaignArtifacts(local, task, t.reason !== "evaluated").map(r => ({ ...r, path: `${e.namespace}/${r.path}` }));
142
+ validateCampaignEpochArtifacts(t.artifacts, expected);
143
+ if (t.reason === "provider-limit")
144
+ campaignAssert(t.outcome === "agent-limit-reached" && local.realAgent?.providerInvoked === true && local.availability !== "complete" && !local.patch.attempted && local.cleanup.attempted && local.cleanup.removed && local.realAgent.cwdCleanup.attempted && local.realAgent.cwdCleanup.removed && local.protectedIntegrity.status === "intact", "LIMIT_TERMINAL_PROOF");
145
+ if (t.reason === "evaluated") {
146
+ const completion = { sourceSchema: "my-dev-kit-lab-agent-success-rate-execution-v1", evidence: local, taskSuccess: analyzeAgentSuccessTreatment(task, local).metrics.taskSuccess.value === true, artifacts: t.artifacts.map(r => ({ ...r, path: r.path.slice(String(e.namespace).length + 1) })) };
147
+ validateCampaignCompletion(completion, identity, task, String(u.treatmentId));
148
+ }
149
+ }
150
+ });
151
+ const last = u.epochs.at(-1);
152
+ if (u.state === "pending")
153
+ campaignAssert(last === undefined && u.completion === null);
154
+ else {
155
+ campaignAssert(last !== undefined);
156
+ if (u.state === "in-progress")
157
+ campaignAssert(["reserved", "invocation-authorized"].includes(last.phase));
158
+ if (u.state === "uncertain")
159
+ campaignAssert(last.phase === "uncertain");
160
+ if (["completed", "paused-limit", "incomplete"].includes(String(u.state)))
161
+ campaignAssert(last.phase === "terminal" && last.terminal?.reason === (u.state === "completed" ? "evaluated" : u.state === "paused-limit" ? "provider-limit" : "incomplete"));
162
+ if (u.state === "completed")
163
+ campaignAssert(equal(u.completion, { sourceSchema: "my-dev-kit-lab-agent-success-rate-execution-v1", evidence: last.terminal.evidence, taskSuccess: analyzeAgentSuccessTreatment(task, last.terminal.evidence).metrics.taskSuccess.value === true, artifacts: last.terminal.artifacts }));
164
+ else
165
+ campaignAssert(u.completion === null);
166
+ }
167
+ });
168
+ if (value.finalization !== null) {
169
+ campaignExact(value.finalization, ["completedAt", "artifacts"]);
170
+ timestamp(value.finalization.completedAt);
171
+ campaignAssert(value.units.every(u => u.state === "completed") && value.finalization.completedAt <= value.updatedAt);
172
+ validateCampaignEpochArtifacts(value.finalization.artifacts, [{ path: "agent-success-rate-analysis.json", kind: "provider" }, { path: "agent-success-rate-execution.json", kind: "provider" }]);
173
+ }
174
+ const { integrity, ...body } = value;
175
+ campaignAssert(isCampaignDigest(integrity) && integrity === campaignHash(canonicalCampaignJson(body)), "CHECKPOINT_INTEGRITY");
176
+ }
177
+ export function transitionCampaignCheckpointV2(previous, tasks, action, at) {
178
+ validateCampaignCheckpointV2(previous, tasks);
179
+ campaignAssert(previous.finalization === null, "COMPLETED_IMMUTABLE");
180
+ const { integrity, ...body } = structuredClone(previous);
181
+ body.generation++;
182
+ body.parentIntegrity = integrity;
183
+ body.updatedAt = at;
184
+ if (action.kind === "finalize") {
185
+ campaignAssert(body.units.every(u => u.state === "completed"), "INCOMPLETE_CAMPAIGN");
186
+ body.finalization = structuredClone(action.finalization);
187
+ }
188
+ else {
189
+ campaignAssert(Number.isSafeInteger(action.index) && action.index >= 0 && action.index < body.units.length);
190
+ campaignAssert(body.units.slice(0, action.index).every(u => u.state === "completed"), "QUEUE_ORDER");
191
+ const u = body.units[action.index];
192
+ const last = u.epochs.at(-1);
193
+ campaignAssert(u.state !== "completed", "COMPLETED_IMMUTABLE");
194
+ if (action.kind === "reserve") {
195
+ if (action.authorization)
196
+ validateCampaignLimitAuthorization(action.authorization, previous, previous.units[action.index]);
197
+ else
198
+ campaignAssert(u.state === "pending" && u.epochs.length === 0, "UNIT_NOT_PENDING");
199
+ const ordinal = (u.epochs.length + 1);
200
+ const e = { schemaVersion: CAMPAIGN_EPOCH_SCHEMA, caseId: u.caseId, treatmentId: u.treatmentId, ordinal, id: action.epochId, previousEpochId: last?.id ?? null, parentRevision: integrity, phase: "reserved", configuration: structuredClone(previous.identity.config), namespace: campaignEpochNamespace(u.caseId, u.treatmentId, ordinal, action.epochId), authorization: action.authorization ? { intent: "limit-continuation", scope: structuredClone(action.authorization) } : { intent: "initial", scope: null }, reservedAt: at, authorizedAt: null, terminalAt: null, terminal: null };
201
+ u.epochs.push(e);
202
+ u.state = "in-progress";
203
+ }
204
+ else {
205
+ campaignAssert(last && u.state === "in-progress", "INVALID_EPOCH_TRANSITION");
206
+ if (action.kind === "authorize") {
207
+ campaignAssert(last.phase === "reserved");
208
+ last.phase = "invocation-authorized";
209
+ last.authorizedAt = at;
210
+ }
211
+ else {
212
+ campaignAssert(last.phase === "invocation-authorized");
213
+ if (action.kind === "uncertain") {
214
+ last.phase = "uncertain";
215
+ u.state = "uncertain";
216
+ }
217
+ else {
218
+ last.phase = "terminal";
219
+ last.terminalAt = at;
220
+ last.terminal = structuredClone(action.terminal);
221
+ u.state = action.terminal.reason === "evaluated" ? "completed" : action.terminal.reason === "provider-limit" ? "paused-limit" : "incomplete";
222
+ if (u.state === "completed")
223
+ u.completion = { sourceSchema: "my-dev-kit-lab-agent-success-rate-execution-v1", evidence: structuredClone(action.terminal.evidence), taskSuccess: analyzeAgentSuccessTreatment(tasks[Math.floor(action.index / 2)], action.terminal.evidence).metrics.taskSuccess.value === true, artifacts: structuredClone(action.terminal.artifacts) };
224
+ }
225
+ }
226
+ }
227
+ }
228
+ const result = sealCampaignCheckpointV2(body);
229
+ validateCampaignCheckpointV2(result, tasks);
230
+ return result;
231
+ }
232
+ /** Reconstruct the exact permitted transition; no last-writer-wins or arbitrary unit replacement. */
233
+ export function validateCampaignHistoryV2(previous, next, tasks) {
234
+ let action;
235
+ if (previous.finalization === null && next.finalization !== null)
236
+ action = { kind: "finalize", finalization: next.finalization };
237
+ else {
238
+ const changes = next.units.map((u, i) => equal(u, previous.units[i]) ? -1 : i).filter(i => i >= 0);
239
+ campaignAssert(changes.length === 1, "INVALID_HISTORY");
240
+ const index = changes[0];
241
+ const a = previous.units[index], b = next.units[index];
242
+ const e = b.epochs.at(-1);
243
+ campaignAssert(e !== undefined, "INVALID_HISTORY");
244
+ if (b.epochs.length === a.epochs.length + 1)
245
+ action = { kind: "reserve", index, epochId: e.id, ...(e.authorization.intent === "limit-continuation" ? { authorization: e.authorization.scope } : {}) };
246
+ else if (e.phase === "invocation-authorized")
247
+ action = { kind: "authorize", index };
248
+ else if (e.phase === "uncertain")
249
+ action = { kind: "uncertain", index };
250
+ else {
251
+ campaignAssert(e.phase === "terminal" && e.terminal !== null, "INVALID_HISTORY");
252
+ action = { kind: "terminal", index, terminal: e.terminal };
253
+ }
254
+ }
255
+ campaignAssert(equal(transitionCampaignCheckpointV2(previous, tasks, action, next.updatedAt), next), "INVALID_HISTORY");
256
+ }
@@ -0,0 +1,207 @@
1
+ import { rmdir } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
3
+ import { runAgentPrompt } from "../../../agents/runAgentPrompt.js";
4
+ import { campaignAssert, canonicalCampaignJson, compareCampaignIdentity } from "./campaignIdentity.js";
5
+ import { buildCampaignQueue } from "./campaignQueue.js";
6
+ import { createCampaignCheckpointV2, transitionCampaignCheckpointV2, validateCampaignLimitAuthorization } from "./campaignRecovery.js";
7
+ import { acquireCampaignDispatch, allocateCampaignEpoch, assertCampaignDispatch, persistCampaignSupportingArtifacts, prepareCampaignEpochTerminal, prepareCampaignProviderOutput, publishCampaignFinalArtifacts, readCampaignCheckpoint, readCampaignCheckpointV2, releaseCampaignDispatch, writeCampaignCheckpointV2 } from "./campaignCheckpointStore.js";
8
+ import { validatePreparedCampaign } from "./campaignExecution.js";
9
+ import { boundMessage, executeAgentSuccessTreatment, resolveControlledBenchmarkProject, resolveSandboxRuntimeRoot } from "./execution.js";
10
+ import { executeAgentSuccessRepairTreatment } from "./repairExecution.js";
11
+ import { agentSuccessAgentArtifactDirectory, createRealAgentProposalSource } from "./realAgentExecution.js";
12
+ import { defaultContextDependencies, sealTask } from "./plugin.js";
13
+ import { projectAgentFacingTask } from "./agentTaskProjection.js";
14
+ import { AgentSuccessPackContextBuilder, buildContextTaskInput, buildRawFullFileContext, DEFAULT_AGENT_SUCCESS_KIT_COMMAND } from "./contextGeneration.js";
15
+ import { analyzeAgentSuccessRate } from "./analysis.js";
16
+ import { buildAgentSuccessRateExecutionArtifact, validateAgentSuccessRateExecutionArtifact, AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE } from "./executionArtifact.js";
17
+ import { buildAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateAnalysisArtifact, validateAgentSuccessRateArtifactFamily, AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE } from "./analysisArtifact.js";
18
+ function blocked(outcome, unit) {
19
+ const cp = outcome.checkpoint;
20
+ const epoch = unit.epochs.at(-1);
21
+ outcome.status = unit.state === "paused-limit" ? "provider-limit" : "recovery-required";
22
+ outcome.blockedUnit = { caseId: unit.caseId, treatmentId: unit.treatmentId, generation: cp.generation, revision: cp.integrity, phase: epoch?.phase ?? "pending", reason: unit.state === "paused-limit" ? "provider-limit" : epoch?.phase === "reserved" ? "reserved-not-replayed" : unit.state === "incomplete" ? "terminal-incomplete" : "potentially-invoked" };
23
+ }
24
+ export async function executeResumableCampaign(prepared, args) {
25
+ const outcome = { status: "failed", checkpoint: null, errors: [], scientificArtifactsWritten: false, blockedUnit: null };
26
+ let owner;
27
+ let verified;
28
+ let packBuilder;
29
+ let activeRuntime;
30
+ const at = () => new Date().toISOString();
31
+ try {
32
+ campaignAssert(args.intent && ["new-resumable", "resume", "authorize-limit-continuation"].includes(args.intent.kind), "EXPLICIT_INTENT_REQUIRED");
33
+ // The closed runtime intent also rejects force-replay or a generic resume boolean.
34
+ campaignAssert(Object.keys(args.intent).sort().join(",") === (args.intent.kind === "authorize-limit-continuation" ? "authorization,kind" : "kind"), "INVALID_INTENT");
35
+ verified = await validatePreparedCampaign(prepared);
36
+ const { input, identity } = verified;
37
+ const { boundary, tasks, provider } = input;
38
+ if (provider.kind === "real-agent")
39
+ campaignAssert(provider.includeRealAgents === true && provider.trustedExecutable && provider.trustedExecutable.providerId === provider.providerId, "REAL_PROVIDER_AUTHORIZATION_REQUIRED");
40
+ const queue = buildCampaignQueue(tasks, input.treatments, "all-selected-cases");
41
+ campaignAssert(["one-case", "all-selected-cases"].includes(args.limit), "INVALID_EXECUTION_LIMIT");
42
+ owner = await acquireCampaignDispatch(boundary, identity);
43
+ const fresh = async () => {
44
+ const current = await validatePreparedCampaign(prepared);
45
+ campaignAssert(compareCampaignIdentity(identity, current.identity) === "MATCH", "IDENTITY_CHANGED_BEFORE_DISPATCH");
46
+ await assertCampaignDispatch(boundary, identity, owner);
47
+ };
48
+ const read = async () => {
49
+ await fresh();
50
+ const stored = await readCampaignCheckpointV2(boundary, identity, tasks);
51
+ campaignAssert(stored.identityVerification === "MATCH", "IDENTITY_UNVERIFIABLE");
52
+ campaignAssert(stored.temporaryFiles.length === 0, "CHECKPOINT_PUBLICATION_UNRESOLVED");
53
+ return stored;
54
+ };
55
+ const publish = async (cp) => {
56
+ await fresh();
57
+ await writeCampaignCheckpointV2(boundary, cp, tasks, phase => args.hooks?.publication?.(cp.generation, phase), owner);
58
+ const stored = await read();
59
+ campaignAssert(stored.checkpoint?.integrity === cp.integrity, "CLAIM_READBACK_FAILED");
60
+ outcome.checkpoint = stored.checkpoint;
61
+ };
62
+ const stored = await read();
63
+ if (args.intent.kind === "new-resumable") {
64
+ campaignAssert(stored.checkpoint === null, "CAMPAIGN_ALREADY_EXISTS");
65
+ // No copy or migration, even when a historical chain happens to share the identity.
66
+ const legacy = await readCampaignCheckpoint(boundary, identity, tasks);
67
+ campaignAssert(legacy.checkpoint === null && legacy.temporaryFiles.length === 0, "LEGACY_CAMPAIGN_READ_ONLY");
68
+ await publish(createCampaignCheckpointV2(identity, tasks, args.runId, at()));
69
+ }
70
+ else {
71
+ campaignAssert(stored.reusable && stored.checkpoint !== null, "RESUMABLE_CAMPAIGN_REQUIRED_NO_V1_MIGRATION");
72
+ outcome.checkpoint = stored.checkpoint;
73
+ }
74
+ const alreadyComplete = outcome.checkpoint.units.every(u => u.state === "completed");
75
+ // Validate a continuation request before accepting any complete/blocked/no-work shortcut.
76
+ let authorizedIndex;
77
+ if (args.intent.kind === "authorize-limit-continuation") {
78
+ authorizedIndex = outcome.checkpoint.units.findIndex(u => u.state !== "completed");
79
+ campaignAssert(authorizedIndex >= 0, "COMPLETED_IMMUTABLE");
80
+ validateCampaignLimitAuthorization(args.intent.authorization, outcome.checkpoint, outcome.checkpoint.units[authorizedIndex]);
81
+ }
82
+ if (!alreadyComplete && provider.kind === "trusted-simulated-provider")
83
+ packBuilder = new AgentSuccessPackContextBuilder("trusted-simulated-kit", provider.contextDependencies);
84
+ if (!alreadyComplete && provider.kind === "real-agent")
85
+ packBuilder = new AgentSuccessPackContextBuilder(provider.kitCommand ?? DEFAULT_AGENT_SUCCESS_KIT_COMMAND, provider.contextDependencies ?? defaultContextDependencies());
86
+ let caseProcessed;
87
+ for (const unit of queue) {
88
+ let state = outcome.checkpoint.units[unit.checkpointIndex];
89
+ if (state.state === "completed")
90
+ continue;
91
+ if (args.limit === "one-case" && caseProcessed !== undefined && caseProcessed !== unit.task.id)
92
+ break;
93
+ if (state.state !== "pending" && authorizedIndex !== unit.checkpointIndex) {
94
+ blocked(outcome, state);
95
+ return outcome;
96
+ }
97
+ caseProcessed = unit.task.id;
98
+ // Read and verify the whole chain again before reserving. No local snapshot alone authorizes dispatch.
99
+ const latest = (await read()).checkpoint;
100
+ campaignAssert(latest.integrity === outcome.checkpoint.integrity, "STALE_REVISION");
101
+ const reserve = transitionCampaignCheckpointV2(latest, tasks, { kind: "reserve", index: unit.checkpointIndex, epochId: randomUUID(), ...(authorizedIndex === unit.checkpointIndex && args.intent.kind === "authorize-limit-continuation" ? { authorization: args.intent.authorization } : {}) }, at());
102
+ const epoch = reserve.units[unit.checkpointIndex].epochs.at(-1);
103
+ const epochRoot = await allocateCampaignEpoch(boundary, identity, owner, epoch);
104
+ await publish(reserve);
105
+ // Context preparation occurs before provider authorization; a crash here does not create an implicit replay policy.
106
+ const forbidden = new Map();
107
+ const executionTask = provider.kind === "deterministic-fixture" ? unit.task : sealTask(unit.task, forbidden);
108
+ const canonicalProjectRoot = await resolveControlledBenchmarkProject(boundary.toolRoot, unit.task.benchmarkProject);
109
+ const contextInput = buildContextTaskInput(executionTask);
110
+ const contexts = packBuilder ? { "raw-full-file": await buildRawFullFileContext(contextInput, canonicalProjectRoot), "context-pack": await packBuilder.build(contextInput, canonicalProjectRoot) } : null;
111
+ activeRuntime = await resolveSandboxRuntimeRoot(epochRoot, boundary.toolRoot);
112
+ await publish(transitionCampaignCheckpointV2(outcome.checkpoint, tasks, { kind: "authorize", index: unit.checkpointIndex }, at()));
113
+ await fresh();
114
+ const claimed = (await read()).checkpoint;
115
+ state = claimed.units[unit.checkpointIndex];
116
+ campaignAssert(claimed.integrity === outcome.checkpoint.integrity && state.epochs.at(-1).id === epoch.id && state.epochs.at(-1).phase === "invocation-authorized", "INVOCATION_NOT_AUTHORIZED");
117
+ const common = { task: executionTask, treatmentId: unit.treatmentId, runId: `${outcome.checkpoint.runId}-${epoch.id}`, canonicalProjectRoot, runtimeRoot: activeRuntime, privateRoots: [boundary.outputRoot, boundary.toolRoot], dependencies: input.dependencies };
118
+ // Explicit fixture branch only. Missing provider context never selects a fixture patch.
119
+ campaignAssert(provider.kind === "deterministic-fixture" || contexts !== null, "PROVIDER_CONTEXT_REQUIRED");
120
+ const result = provider.kind !== "deterministic-fixture" && contexts ? await executeAgentSuccessRepairTreatment({ ...common, repair: {
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) => {
123
+ await fresh();
124
+ campaignAssert((await read()).checkpoint?.integrity === claimed.integrity, "STALE_INVOCATION_CLAIM");
125
+ await prepareCampaignProviderOutput(boundary, identity, owner, `${epoch.namespace}/${agentSuccessAgentArtifactDirectory(unit.task.benchmarkProject, unit.task.id, unit.treatmentId, attemptNumber)}`);
126
+ return provider.kind === "real-agent" ? runAgentPrompt(request) : provider.runAgent(request);
127
+ } }, runId: common.runId, task: projectAgentFacingTask(executionTask), contexts, forbidden: forbidden.get(unit.task.id) ?? [], attemptNumber, ...(feedback ? { repair: { feedback, maxAttempts: 1 + provider.repairAttempts } } : {}) })
128
+ } }) : await executeAgentSuccessTreatment(common);
129
+ await rmdir(activeRuntime);
130
+ activeRuntime = undefined;
131
+ await fresh();
132
+ await persistCampaignSupportingArtifacts(boundary, identity, owner, unit.task, result, epoch);
133
+ const reason = result.evidence.realAgent?.providerStatus === "agent-limit-reached" ? "provider-limit" : result.evidence.status === "completed" && result.evidence.availability === "complete" ? "evaluated" : "incomplete";
134
+ const terminal = await prepareCampaignEpochTerminal(boundary, identity, unit.task, epoch, result.evidence, reason);
135
+ await publish(transitionCampaignCheckpointV2(outcome.checkpoint, tasks, { kind: "terminal", index: unit.checkpointIndex, terminal }, at()));
136
+ authorizedIndex = undefined;
137
+ if (reason !== "evaluated") {
138
+ blocked(outcome, outcome.checkpoint.units[unit.checkpointIndex]);
139
+ return outcome;
140
+ }
141
+ }
142
+ const complete = outcome.checkpoint.units.every(u => u.state === "completed");
143
+ if (!complete) {
144
+ outcome.status = "partial";
145
+ return outcome;
146
+ }
147
+ const cp = (await read()).checkpoint;
148
+ outcome.checkpoint = cp;
149
+ {
150
+ const cases = tasks.map((task, i) => ({ caseId: task.id, caseName: task.title, benchmarkProject: task.benchmarkProject, taskLocality: task.taskLocality, fixtureId: provider.kind === "deterministic-fixture" ? task.deterministicFixture.id : "", treatments: [cp.units[i * 2].completion.evidence, cp.units[i * 2 + 1].completion.evidence] }));
151
+ const analysis = analyzeAgentSuccessRate(tasks, cases, provider.kind === "deterministic-fixture" ? "deterministic-fixture" : "real-agent");
152
+ // Stable final metadata also permits safe recovery after a partial final artifact publication.
153
+ const completedAt = cp.finalization?.completedAt ?? cp.updatedAt;
154
+ const common = { runId: cp.runId, pluginId: "agent-success-rate", pluginSchemaVersion: "1.0.0", startedAt: cp.createdAt, completedAt };
155
+ const execution = buildAgentSuccessRateExecutionArtifact({ ...common, cases, ...(provider.kind === "deterministic-fixture" ? {} : { realAgent: { providerId: provider.providerId, timeoutMs: provider.timeoutMs, repairAttempts: provider.repairAttempts, attemptsPerTreatment: (1 + provider.repairAttempts), promptTransport: "stdin", contextEffectEvaluated: analysis.contextEffectEvaluated } }) });
156
+ const scientific = buildAgentSuccessRateAnalysisArtifact({ ...common, analysis });
157
+ campaignAssert([...validateAgentSuccessRateExecutionArtifact(execution), ...validateAgentSuccessRateAnalysisArtifact(scientific), ...validateAgentSuccessRateArtifactFamily(execution, scientific)].length === 0, "SCIENTIFIC_ARTIFACT_INVALID");
158
+ const finalization = await publishCampaignFinalArtifacts(boundary, identity, owner, [{ relativePath: AGENT_SUCCESS_RATE_EXECUTION_ARTIFACT_FILE, content: `${JSON.stringify(execution, null, 2)}\n` }, { relativePath: AGENT_SUCCESS_RATE_ANALYSIS_ARTIFACT_FILE, content: `${JSON.stringify(scientific, null, 2)}\n` }], cp.runId, completedAt);
159
+ if (cp.finalization === null)
160
+ await publish(transitionCampaignCheckpointV2(cp, tasks, { kind: "finalize", finalization }, at()));
161
+ else
162
+ campaignAssert(canonicalCampaignJson(finalization) === canonicalCampaignJson(cp.finalization), "FINAL_ARTIFACT_INCONSISTENT");
163
+ }
164
+ outcome.scientificArtifactsWritten = true;
165
+ outcome.status = alreadyComplete ? "already-complete" : "completed";
166
+ }
167
+ catch (error) {
168
+ outcome.status = "failed";
169
+ outcome.errors.push(boundMessage(error instanceof Error ? error.message : "CAMPAIGN_FAILED", verified ? [verified.input.boundary.outputRoot, verified.input.boundary.toolRoot, verified.input.sourceRoot ?? ""] : []));
170
+ if (owner && verified)
171
+ try {
172
+ outcome.checkpoint = (await readCampaignCheckpointV2(verified.input.boundary, verified.identity, verified.input.tasks)).checkpoint;
173
+ }
174
+ catch {
175
+ outcome.errors.push("CHECKPOINT_READ_FAILED");
176
+ }
177
+ }
178
+ finally {
179
+ if (packBuilder)
180
+ try {
181
+ campaignAssert((await packBuilder.dispose()) === null, "CONTEXT_CLEANUP_FAILED");
182
+ }
183
+ catch {
184
+ outcome.errors.push("CONTEXT_CLEANUP_FAILED");
185
+ outcome.status = "failed";
186
+ }
187
+ if (activeRuntime)
188
+ try {
189
+ await rmdir(activeRuntime);
190
+ }
191
+ catch (error) {
192
+ if (error.code !== "ENOENT") {
193
+ outcome.errors.push("RUNTIME_ROOT_NOT_REMOVED");
194
+ outcome.status = "failed";
195
+ }
196
+ }
197
+ if (owner && verified)
198
+ try {
199
+ await releaseCampaignDispatch(verified.input.boundary, verified.identity, owner);
200
+ }
201
+ catch {
202
+ outcome.errors.push("DISPATCH_RELEASE_FAILED");
203
+ outcome.status = "failed";
204
+ }
205
+ }
206
+ return outcome;
207
+ }
@@ -0,0 +1,123 @@
1
+ import { parseCommandString } from "../../../core/commandLine.js";
2
+ import { invalidExperimentConfig, isPlainObject, mergeConfig, validExperimentConfig } from "../../config.js";
3
+ import { DEFAULT_AGENT_SUCCESS_KIT_COMMAND } from "./contextGeneration.js";
4
+ import { AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS } from "./realAgentExecution.js";
5
+ export const AGENT_SUCCESS_RATE_REAL_AGENT_IDS = ["codex", "claude"];
6
+ export const AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES = [0, 1, 2];
7
+ export const defaultAgentSuccessRateConfig = {
8
+ outDir: "lab-output/agent-success-rate"
9
+ };
10
+ const SUPPORTED_FIELDS = ["outDir", "caseIds", "benchmarkProjects", "agentId", "includeRealAgents", "timeoutMs", "kitCommand", "repairAttempts"];
11
+ /** Names that are recognized only to give a precise rejection. */
12
+ const REJECTED_FIELD_MESSAGES = {
13
+ commandTemplate: "commandTemplate is not supported; real providers use the fixed adapter invocation.",
14
+ campaignPreset: "campaignPreset is not supported by agent-success-rate.",
15
+ retryUntilSuccess: "retryUntilSuccess is not supported; repair attempts are bounded by repairAttempts (maximum 2).",
16
+ unlimitedRepairs: "unlimitedRepairs is not supported; repair attempts are bounded by repairAttempts (maximum 2).",
17
+ providerMatrix: "providerMatrix is not supported; exactly one provider is selected per run.",
18
+ resumeSession: "resumeSession is not supported.",
19
+ resumeFromCheckpoint: "resumeFromCheckpoint is not supported.",
20
+ automaticProviderSwitching: "automaticProviderSwitching is not supported; a repair always uses the selected provider.",
21
+ strategies: "strategies is not supported; the two treatments are fixed.",
22
+ agentIds: "agentIds is not supported; exactly one provider is selected per run.",
23
+ agents: "agents is not supported; exactly one provider is selected per run."
24
+ };
25
+ export const agentSuccessRateConfigDefinition = {
26
+ fields: [
27
+ { name: "outDir", type: "string", required: true, description: "Experiment output root." },
28
+ { name: "caseIds", type: "array", description: "Task IDs to include; input order is preserved." },
29
+ { name: "benchmarkProjects", type: "array", description: "Benchmark project IDs to include; input order is preserved." },
30
+ { name: "agentId", type: "string", description: "Real provider for real-agent mode: codex or claude. Absent selects deterministic-fixture mode." },
31
+ { name: "includeRealAgents", type: "boolean", description: "Must be exactly true whenever agentId is set; rejected otherwise." },
32
+ { name: "timeoutMs", type: "number", description: "Per-attempt provider timeout in ms (real-agent mode only)." },
33
+ { 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." }
35
+ ]
36
+ };
37
+ export function validateAgentSuccessRateConfig(config) {
38
+ if (config !== undefined && !isPlainObject(config)) {
39
+ return invalidExperimentConfig(["agent success rate config must be an object."]);
40
+ }
41
+ const errors = [];
42
+ const raw = isPlainObject(config) ? config : {};
43
+ const unsupported = Object.keys(raw).filter((key) => !SUPPORTED_FIELDS.includes(key));
44
+ const explained = unsupported.filter((key) => key in REJECTED_FIELD_MESSAGES);
45
+ const unknown = unsupported.filter((key) => !(key in REJECTED_FIELD_MESSAGES));
46
+ for (const key of explained.sort())
47
+ errors.push(REJECTED_FIELD_MESSAGES[key]);
48
+ if (unknown.length > 0) {
49
+ errors.push(`Unsupported agent-success-rate config field(s): ${unknown.sort().join(", ")}.`);
50
+ }
51
+ const normalized = mergeConfig(defaultAgentSuccessRateConfig, config);
52
+ const outDir = normalized.outDir;
53
+ if (typeof outDir !== "string" || !outDir.trim())
54
+ errors.push("outDir must be a non-empty string.");
55
+ for (const field of ["caseIds", "benchmarkProjects"]) {
56
+ const value = normalized[field];
57
+ if (value === undefined)
58
+ continue;
59
+ if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && item.trim())) {
60
+ errors.push(`${field} must be a non-empty array of non-empty strings when provided.`);
61
+ }
62
+ else if (new Set(value).size !== value.length) {
63
+ errors.push(`${field} must not contain duplicate entries.`);
64
+ }
65
+ }
66
+ // Real-agent fields are validated against the RAW object so defaults can never look like an opt-in.
67
+ const agentId = raw.agentId;
68
+ if (agentId === undefined) {
69
+ if (raw.includeRealAgents === true)
70
+ errors.push("includeRealAgents requires agentId; real-agent mode is entered only by naming one provider.");
71
+ else if (raw.includeRealAgents !== undefined && typeof raw.includeRealAgents !== "boolean")
72
+ errors.push("includeRealAgents must be a boolean.");
73
+ if (raw.timeoutMs !== undefined)
74
+ errors.push("timeoutMs is only supported together with agentId.");
75
+ if (raw.kitCommand !== undefined)
76
+ errors.push("kitCommand is only supported together with agentId.");
77
+ if (raw.repairAttempts !== undefined) {
78
+ if (!isRepairAttemptsValue(raw.repairAttempts))
79
+ errors.push("repairAttempts must be 0, 1 or 2.");
80
+ else if (raw.repairAttempts > 0)
81
+ errors.push("repairAttempts greater than 0 requires real-agent mode (agentId together with includeRealAgents=true); deterministic-fixture mode has no repair.");
82
+ }
83
+ }
84
+ else {
85
+ if (Array.isArray(agentId)) {
86
+ errors.push("agentId must name exactly one provider (codex or claude); multiple providers are not supported.");
87
+ }
88
+ else if (typeof agentId !== "string" || !AGENT_SUCCESS_RATE_REAL_AGENT_IDS.includes(agentId)) {
89
+ errors.push(`agentId must be one of: ${AGENT_SUCCESS_RATE_REAL_AGENT_IDS.join(", ")}.`);
90
+ }
91
+ if (raw.includeRealAgents !== true)
92
+ errors.push("agentId requires includeRealAgents to be exactly true.");
93
+ if (raw.timeoutMs !== undefined) {
94
+ const timeoutMs = raw.timeoutMs;
95
+ if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || !Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS) {
96
+ errors.push(`timeoutMs must be a finite positive integer no greater than ${AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS}.`);
97
+ }
98
+ }
99
+ if (raw.repairAttempts !== undefined && !isRepairAttemptsValue(raw.repairAttempts))
100
+ errors.push("repairAttempts must be 0, 1 or 2.");
101
+ if (raw.kitCommand !== undefined) {
102
+ const kitCommand = raw.kitCommand;
103
+ let supported = false;
104
+ if (typeof kitCommand === "string" && kitCommand.trim()) {
105
+ try {
106
+ supported = parseCommandString(kitCommand).executable.length > 0;
107
+ }
108
+ catch {
109
+ supported = false;
110
+ }
111
+ }
112
+ if (!supported)
113
+ errors.push("kitCommand must be a non-empty my-dev-kit command string.");
114
+ }
115
+ else {
116
+ normalized.kitCommand = DEFAULT_AGENT_SUCCESS_KIT_COMMAND;
117
+ }
118
+ }
119
+ return errors.length > 0 ? invalidExperimentConfig(errors) : validExperimentConfig(normalized);
120
+ }
121
+ function isRepairAttemptsValue(value) {
122
+ return typeof value === "number" && AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES.includes(value);
123
+ }