@dailephd/my-dev-kit-lab 0.9.0 → 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.
- package/CHANGELOG.md +9 -0
- package/README.md +4 -3
- package/dist/src/agents/adapters/claudeAdapter.js +6 -3
- package/dist/src/agents/adapters/codexAdapter.js +51 -10
- package/dist/src/agents/index.js +1 -0
- package/dist/src/agents/normalizeAgentTelemetry.js +50 -0
- package/dist/src/agents/parseAgentTokenUsage.js +71 -49
- package/dist/src/agents/providerBuildObservation.js +97 -0
- package/dist/src/audits/core/projectInventory.js +1 -0
- package/dist/src/cli/help.js +8 -2
- package/dist/src/commands/runExperimentDescribeCommand.js +31 -0
- package/dist/src/commands/runExperimentRunCommand.js +192 -5
- package/dist/src/core/runMeasuredCommand.js +4 -0
- package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +3 -1
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpoint.js +216 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +588 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignEvidenceQuality.js +77 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignExecution.js +314 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignIdentity.js +111 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignProgress.js +173 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignQueue.js +11 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignRecovery.js +256 -0
- package/dist/src/experiments/plugins/agentSuccessRate/campaignResumableExecution.js +207 -0
- package/dist/src/experiments/plugins/agentSuccessRate/index.js +6 -0
- package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +32 -22
- package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +29 -7
- package/dist/src/experiments/plugins/agentSuccessRate/timeoutPresets.js +9 -0
- package/dist/src/report/experiments/agentSuccessCampaignProgressReportModel.js +1 -0
- package/dist/src/report/experiments/buildAgentSuccessCampaignProgressReport.js +6 -0
- package/dist/src/report/experiments/buildAgentSuccessRateReport.js +11 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +1 -1
- package/dist/src/report/experiments/renderAgentSuccessCampaignProgressHtml.js +6 -0
- package/dist/src/report/experiments/renderAgentSuccessCampaignProgressText.js +25 -0
- package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +3 -1
- package/dist/src/report/experiments/renderAgentSuccessRateText.js +7 -0
- package/dist/src/report/experiments/writeAgentSuccessCampaignProgressReport.js +135 -0
- package/dist/src/report/experiments/writePluginExperimentReports.js +4 -3
- package/docs/METRICS.md +9 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to my-dev-kit-lab are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.9.1] - 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Normalized provider telemetry and resumable campaigns
|
|
8
|
+
|
|
9
|
+
- Normalized provider token and duration provenance while preserving legacy parser compatibility and separating unavailable telemetry from task outcomes.
|
|
10
|
+
- Added deterministic agent-success-rate campaign queues, immutable checkpoint generations, version 1 compatibility and version 2 recovery epochs, explicit resume and verified completed-unit reuse, and bounded provider-limit continuation.
|
|
11
|
+
- Added public campaign CLI options, Codex and Claude timeout presets, campaign progress summaries, and strong/partial/unavailable metric-specific evidence classifications.
|
|
12
|
+
- Preserved partial evidence without presenting it as complete scientific analysis; uncertain provider invocations are not replayed automatically. Real-provider use remains explicitly opted in and depends on the existing provider adapters.
|
|
13
|
+
|
|
5
14
|
## [Unreleased]
|
|
6
15
|
|
|
7
16
|
## [0.9.0] - 2026-10-10
|
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@ my-dev-kit provides local repository indexing and graph-guided retrieval. my-dev
|
|
|
6
6
|
|
|
7
7
|
Whole-ecosystem workflows are centralized in [my-dev-kit/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md](https://github.com/dailephd/my-dev-kit/blob/main/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md). Its [command-surface compatibility map](https://github.com/dailephd/my-dev-kit/blob/main/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md#915-command-surface-compatibility-map) documents which Lab outputs can safely seed my-dev-kit investigation, Observer reference work, or Orchestrator evidence, and which look-alike artifacts are not direct inputs.
|
|
8
8
|
|
|
9
|
-
The latest release is v0.9.
|
|
9
|
+
The latest release is v0.9.1 (normalized provider telemetry and resumable agent-success-rate campaigns), and the repository package version is 0.9.1. The previous release is v0.9.0 (agent-success-rate implementation-patch evaluation). The v0.4.5 context-integrity evaluation remains intentionally frozen against the published `@dailephd/my-dev-kit@1.10.4` and `@dailephd/my-dev-kit-orchestrator@1.2.3` contracts; those versions are historical validation baselines, not a statement that they are the ecosystem's current releases. See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for current state.
|
|
10
10
|
|
|
11
11
|
v0.9.0 adds the `agent-success-rate` plugin, the eighth registered experiment plugin, which evaluates implementation patches under the `raw-full-file` and `context-pack` treatments.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
v0.9.1 adds normalized provider telemetry and resumable campaign execution. v0.9.2 remains planned.
|
|
14
14
|
|
|
15
15
|
v0.4.6 adds a supported `my-dev-kit-lab` installed CLI router (`--help`, `--version`, `security validate`, `audit`, the `experiment` family, `report render`, `plots generate`, `gallery build`, `demo final`, and the historical direct final-demo invocation form), a writable lab workspace model kept separate from the installed package and the inspected target, and a permanent packed-tarball installation/execution acceptance gate (`npm run verify:packed-package`). See [docs/ROADMAP.md](docs/ROADMAP.md) and [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md) for status detail and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the runtime path model. The "Installed CLI" section below documents the shipped command surface; the source-checkout `npm run` workflow in Quickstart remains available for contributors.
|
|
16
16
|
|
|
@@ -27,6 +27,7 @@ v0.4.6 adds a supported `my-dev-kit-lab` installed CLI router (`--help`, `--vers
|
|
|
27
27
|
- **Measure retrieval precision and recall (v0.8.0, released):** the `retrieval-precision-recall` plugin runs the existing my-dev-kit search, lookup, slice, and source lifecycle for each case and compares the retrieved files and symbols with a deterministic answer key, without invoking any agent. It reports file precision and recall, symbol precision and recall, fact coverage (from explicit fact-to-context mappings), irrelevant context ratio, the estimated retrieved token count, and the missed and irrelevant identities, as `retrieval-precision-recall-execution.json` plus `report.json`, `report.html`, and `report.txt`. By default it runs a bundled 12-case corpus over two benchmark projects against the Lab itself. It can also run over an explicitly selected local Git repository with `--target <repository> --local-subject-config <path>`; in that mode the repository is treated as read-only and durable outputs redact the repository path and file, symbol, and fact identities while keeping the numeric evidence. These are set-based measures with no ranking, winner, or composite score. See [docs/COMMANDS.md](docs/COMMANDS.md), [docs/WORKFLOWS.md](docs/WORKFLOWS.md), and [docs/METRICS.md](docs/METRICS.md).
|
|
28
28
|
- **Compare retrieval query strategies (v0.8.1, released):** the `retrieval-query-strategy-comparison` plugin compares seven fixed my-dev-kit retrieval strategies without running any coding agent: `keyword-search`, `symbol-lookup`, `graph-neighborhood`, `source-slice`, `data-model-graph`, `model-view-lineage`, and `combined-graph-guided`. In bundled mode it uses the same frozen 12-case warm-index benchmark corpus as `retrieval-precision-recall`, and every selected case receives all seven strategies (filters narrow cases or projects, never strategies). File and symbol quality keeps the inherited v0.8.0 set-based semantics and adds balanced file and symbol F1 (`2TP / (2TP + FP + FN)`); fact coverage and the estimated retrieved token count complete the four comparison objectives. Strategies are compared over matched complete cases only, as macro means for four scopes (`overall`, `localized`, `cross-module`, `broad-change`, reusing each case's existing task-locality label), using Pareto dominance on mean file F1, mean symbol F1, mean fact coverage (all maximized) and mean retrieved tokens (minimized). A best strategy is named only when the Pareto front has a single member; when several strategies remain nondominated the report states a tradeoff. There is no scalar or composite score and no ordinal ranking. The plugin can also run over an explicitly selected local Git repository through the existing `LocalRepositorySubject` boundary with `--target <repository> --local-subject-config <path>`; the repository stays read-only and durable output redacts private file, symbol, fact, semantic-node, warning, and case-title identities while preserving the numeric results. It writes `retrieval-query-strategy-comparison-execution.json`, `retrieval-query-strategy-comparison-analysis.json`, and `report.json`, `report.html`, and `report.txt`. No context-pack behavior is included. See [docs/COMMANDS.md](docs/COMMANDS.md), [docs/WORKFLOWS.md](docs/WORKFLOWS.md), and [docs/METRICS.md](docs/METRICS.md#retrieval-query-strategy-comparison-metrics-v081).
|
|
29
29
|
- **Generate auditable task context packs (v0.8.2):** `context-pack-generation` runs both fixed treatments, `raw-full-file` and `context-pack`, for each selected case. A pack contains a task summary, relevant files and symbols, bounded source slices, call relationships, tests, and evidence notes. Analysis reuses retrieval fact coverage and file/symbol precision, recall, and F1, reports estimated context size and paired raw-versus-pack tokens saved, percent saved, and objective deltas, and names no composite winner or ranking. Bundled runs persist pack artifacts and reports include a bounded preview. External-local runs calculate science on real identities, redact before persistence, omit durable pack bodies, and show redacted report previews. See [docs/COMMANDS.md](docs/COMMANDS.md), [docs/WORKFLOWS.md](docs/WORKFLOWS.md), and [docs/METRICS.md](docs/METRICS.md).
|
|
30
|
+
- **Run resumable agent-success-rate campaigns (v0.9.1):** provider telemetry is normalized with provenance, and explicit campaign runs support deterministic case ordering, checkpoints, one-case execution, validated resume, safe completed-unit reuse, provider-limit pauses, authorized bounded continuation, and plugin-specific progress evidence. Campaigns use the existing Codex and Claude adapters only after explicit real-provider opt-in. Checkpoint/progress evidence is operational; complete scientific reports are available only after finalization.
|
|
30
31
|
- **Evaluate implementation patches (v0.9.0):** the `agent-success-rate` plugin runs both `raw-full-file` and `context-pack` treatments over a bundled six-task implementation corpus in disposable benchmark copies and decides success from trusted checks on the changed code. The default deterministic mode applies reference patches and runs no coding agent; real Codex or Claude runs (with optional bounded repair) are explicit opt-in. It runs from the installed CLI or a built source checkout (see [docs/COMMANDS.md](docs/COMMANDS.md#agent-success-rate-v090)).
|
|
31
32
|
- **Assess affected-neighborhood evidence (v0.6.1, released; retained in the current package):** using the baseline my-dev-kit graph of the same prepared warm index (loaded once per session), each task's confirmed modified or missing indexed files, and the baseline symbols those files contain, are mapped to graph nodes. The affected neighborhood is those seed nodes plus their direct one-hop neighbors (edges of any kind, either direction). The task's `expectedFiles` and file-scoped `expectedSymbols` are mapped to graph nodes and compared with that neighborhood, which yields six numeric warm-side metrics (`changedFileCount`, `changedSymbolCount`, `affectedNodeCount`, `affectedEdgeCount`, `taskOverlapCount`, `taskOverlapPercent`) plus categorical evidence: a `related`, `unrelated`, or `unknown` relationship and a `recommended`, `not-indicated`, or `unknown` `reindexRecommendation`. Incomplete evidence is reported as `unknown`, never as unrelated. The assessment is recorded additively in `warm-index-execution.json` and shown in the existing `report.json`/`report.txt`/`report.html` warm-index reports. It is observational only: retrieval, execution status, and provider status are unchanged, nothing is reindexed automatically, and no CLI flag, experiment plugin, plot, or gallery item was added. See [docs/METRICS.md](docs/METRICS.md#affected-neighborhood-metrics-v061) and [docs/WORKFLOWS.md](docs/WORKFLOWS.md#warm-index-reuse-experiment).
|
|
32
33
|
- **Audit repository health:** run conservative code-rot detectors for TypeScript/JavaScript, Python, Java, and Kotlin, or adapt security findings into the common audit report.
|
|
@@ -259,7 +260,7 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
|
|
|
259
260
|
- Claude and Codex token totals are used only when the CLI/run exposes them; missing provider token evidence stays explicitly unavailable (never zero, and never substituted with an estimated context-token count)
|
|
260
261
|
- Codex and Claude campaign runs can produce timeouts, invalid-output, agent-unavailable, or agent-limit-reached outcomes; these are structured provider states, not warm-index infrastructure failures
|
|
261
262
|
- Small projects may make raw-full-file cheaper than my-dev-kit-guided; larger localized tasks are where my-dev-kit is expected to become more useful
|
|
262
|
-
- The generic experiment-plugin framework has eight registered plugins: `context-strategy-comparison`, `warm-index-reuse`, `incremental-change-staleness`, `context-window-scaling`, `retrieval-precision-recall`, `retrieval-query-strategy-comparison`, `context-pack-generation`, and `agent-success-rate` (v0.9.0). The v0.7.0 context-window-scaling plugin is released; v0.7.1 added caller-supplied synthetic repositories; v0.7.2 added an explicitly selected local Git repository. The context-pack-generation plugin shipped in v0.8.2;
|
|
263
|
+
- The generic experiment-plugin framework has eight registered plugins: `context-strategy-comparison`, `warm-index-reuse`, `incremental-change-staleness`, `context-window-scaling`, `retrieval-precision-recall`, `retrieval-query-strategy-comparison`, `context-pack-generation`, and `agent-success-rate` (v0.9.0). The v0.7.0 context-window-scaling plugin is released; v0.7.1 added caller-supplied synthetic repositories; v0.7.2 added an explicitly selected local Git repository. The context-pack-generation plugin shipped in v0.8.2; agent-success-rate shipped in v0.9.0 and gained resumable campaigns in v0.9.1.
|
|
263
264
|
- `retrieval-precision-recall` (v0.8.0) measures set-based retrieval quality, not ranked retrieval: it has no ranking metrics, no retrieval-strategy comparison of its own (the separate `retrieval-query-strategy-comparison` plugin added in v0.8.1 compares strategies), no winner or composite score, and no real agents. Its lifecycle expands only the top search candidate. An external-local run fails as a whole if any case fails a safety or execution check, and then writes no normal artifact or report family. Durable external-local identities are redacted, although the console summary of a successful run can still show the physical output directory. Platform path-length limits can make a run with very long paths fail cleanly instead of executing.
|
|
264
265
|
- `retrieval-query-strategy-comparison` (v0.8.1) runs no coding agent and does not evaluate answer quality. Its retrieved token count is a context-size estimate, not provider billing telemetry. Semantic strategies (`data-model-graph`, `model-view-lineage`) can truthfully be partial or unavailable for a case, and a case is then excluded from the matched cross-strategy comparison for every strategy while its per-strategy evidence is kept. The Pareto front is a set of nondominated strategies in canonical strategy order, not an ordinal ranking. The plugin reports no significance test or confidence interval and selects no strategy automatically. External-local mode rejects `--case` and `--benchmark-project`, builds one private base index per configured case, and fails as a whole, writing no normal artifact or report family, if any safety or privacy check fails.
|
|
265
266
|
- `warm-index-reuse` without `--campaign` still uses the deterministic fake agent only. With `--campaign`, exactly one real provider (Codex or Claude) runs per campaign preset — there is no multi-agent matrix, retry, provider switching, or scheduler. The v0.5.1 expanded corpus is deterministic fake-agent benchmark evidence when run without `--campaign`. No warm-index mode calculates a token-savings percentage, break-even task, winner, or ranking, and component-duration sums are not wall-clock latency
|
|
@@ -54,8 +54,8 @@ export const claudeAdapter = {
|
|
|
54
54
|
};
|
|
55
55
|
/**
|
|
56
56
|
* Parses a single JSON result envelope (`{ "result": "..." }`) when stdout is valid JSON, never
|
|
57
|
-
* the raw envelope itself.
|
|
58
|
-
*
|
|
57
|
+
* the raw envelope itself. Malformed JSON-shaped output stays empty; legacy plain text output
|
|
58
|
+
* retains its fallback.
|
|
59
59
|
*/
|
|
60
60
|
export function parseClaudeFinalAnswer(text) {
|
|
61
61
|
const trimmedInput = text.trim();
|
|
@@ -71,9 +71,12 @@ export function parseClaudeFinalAnswer(text) {
|
|
|
71
71
|
// Valid JSON object without a string result: empty, never the raw envelope.
|
|
72
72
|
return { finalAnswerText: "", finalAnswerParseStatus: "empty" };
|
|
73
73
|
}
|
|
74
|
+
return { finalAnswerText: "", finalAnswerParseStatus: "empty" };
|
|
74
75
|
}
|
|
75
76
|
catch {
|
|
76
|
-
//
|
|
77
|
+
// A malformed envelope is not a legacy text answer.
|
|
78
|
+
if (/^[\[{]/.test(trimmedInput))
|
|
79
|
+
return { finalAnswerText: "", finalAnswerParseStatus: "empty" };
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
return { finalAnswerText: trimmedInput, finalAnswerParseStatus: trimmedInput ? "parsed" : "empty" };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { runMeasuredCommand } from "../../core/runMeasuredCommand.js";
|
|
2
2
|
import { applyPromptToCommandTemplate } from "../runAgentPrompt.js";
|
|
3
3
|
import { parseAgentTokenUsage } from "../parseAgentTokenUsage.js";
|
|
4
|
+
import { trustedProviderLaunch } from "../providerBuildObservation.js";
|
|
4
5
|
// v0.5.2 Batch 2 -- frozen stdin invocation for the real-agent evaluation transport. Every flag
|
|
5
6
|
// here is planner-owned; do not add sandbox/model/approval-policy flags in this batch.
|
|
6
7
|
const CODEX_STDIN_ARGS = [
|
|
@@ -51,13 +52,21 @@ export const codexAdapter = {
|
|
|
51
52
|
/**
|
|
52
53
|
* Recognizes a Codex JSONL event stream (at least one line parses to an object with a string
|
|
53
54
|
* `type`) and, when recognized, uses the LAST `item.completed` `agent_message` text as the final
|
|
54
|
-
* answer -- never the raw JSONL.
|
|
55
|
-
*
|
|
55
|
+
* answer -- never the raw JSONL. JSON-shaped output without an answer stays empty. Plain text
|
|
56
|
+
* retains the legacy fallback. A single formatted JSON event is supported as well.
|
|
56
57
|
*/
|
|
57
58
|
export function parseCodexFinalAnswer(text) {
|
|
58
|
-
let sawEventLine =
|
|
59
|
+
let sawEventLine = /^[\s]*[\[{]/.test(text);
|
|
59
60
|
let lastAgentMessageText = null;
|
|
60
|
-
|
|
61
|
+
const candidates = function* () {
|
|
62
|
+
const trimmed = text.trim();
|
|
63
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("["))
|
|
64
|
+
yield trimmed;
|
|
65
|
+
for (const match of text.matchAll(/[^\r\n]+/g))
|
|
66
|
+
if (match[0].trim() !== trimmed)
|
|
67
|
+
yield match[0];
|
|
68
|
+
};
|
|
69
|
+
for (const line of candidates()) {
|
|
61
70
|
const trimmedLine = line.trim();
|
|
62
71
|
if (!trimmedLine)
|
|
63
72
|
continue;
|
|
@@ -69,7 +78,10 @@ export function parseCodexFinalAnswer(text) {
|
|
|
69
78
|
// Agent output can mix JSONL with incidental non-JSON noise; ignore malformed lines.
|
|
70
79
|
continue;
|
|
71
80
|
}
|
|
72
|
-
if (!parsed || typeof parsed !== "object"
|
|
81
|
+
if (!parsed || typeof parsed !== "object")
|
|
82
|
+
continue;
|
|
83
|
+
sawEventLine = true;
|
|
84
|
+
if (Array.isArray(parsed))
|
|
73
85
|
continue;
|
|
74
86
|
const event = parsed;
|
|
75
87
|
if (typeof event.type !== "string")
|
|
@@ -95,7 +107,10 @@ export function parseCodexFinalAnswer(text) {
|
|
|
95
107
|
export async function runCliAgent(request, adapter) {
|
|
96
108
|
const started = Date.now();
|
|
97
109
|
const command = adapter.buildCommand(request);
|
|
98
|
-
|
|
110
|
+
if (request.trustedExecutable && (request.commandTemplate || request.promptTransport !== "stdin" || adapter.id === "fake-agent"))
|
|
111
|
+
throw new Error("INVALID_TRUSTED_PROVIDER_REQUEST");
|
|
112
|
+
const launch = request.trustedExecutable ? await trustedProviderLaunch(request.trustedExecutable, adapter.id) : null;
|
|
113
|
+
const available = launch !== null || await adapter.isAvailable(request);
|
|
99
114
|
if (!available) {
|
|
100
115
|
const ended = Date.now();
|
|
101
116
|
const status = request.requireAvailable ? "failed" : "skipped";
|
|
@@ -127,18 +142,20 @@ export async function runCliAgent(request, adapter) {
|
|
|
127
142
|
}
|
|
128
143
|
const measured = await runMeasuredCommand({
|
|
129
144
|
commandId: `${adapter.id}-agent-run`,
|
|
130
|
-
commandString: command.command,
|
|
131
|
-
extraArgs: command.args,
|
|
145
|
+
...(launch ? { executable: launch.executable, args: [...launch.prefix, ...command.args], resolveCommand: false } : { commandString: command.command, extraArgs: command.args }),
|
|
132
146
|
cwd: request.commandTemplate?.cwd ?? request.cwd,
|
|
133
147
|
outDir: request.outDir,
|
|
134
148
|
env: request.env,
|
|
135
149
|
timeoutMs: request.timeoutMs,
|
|
136
|
-
stdinText: command.stdinText
|
|
150
|
+
stdinText: command.stdinText,
|
|
151
|
+
...(launch ? { stdoutMaxBytes: 8 * 1024 * 1024, stderrMaxBytes: 1024 * 1024, terminateProcessTree: true } : {})
|
|
137
152
|
});
|
|
138
153
|
const ended = Date.now();
|
|
139
154
|
const combinedOutput = `${measured.stdout}\n${measured.stderr}`;
|
|
140
155
|
const parsedAnswer = adapter.parseFinalAnswer(measured.stdout || measured.stderr);
|
|
141
156
|
const parsedUsage = adapter.parseTokenUsage(combinedOutput);
|
|
157
|
+
// Bound campaigns require process-channel diagnostics. Never search final agent message/patch bodies.
|
|
158
|
+
const condition = launch ? trustedProcessCondition(measured.stdout, measured.stderr, measured.timedOut === true) : null;
|
|
142
159
|
return {
|
|
143
160
|
runId: request.runId,
|
|
144
161
|
agentId: adapter.id,
|
|
@@ -164,6 +181,30 @@ export async function runCliAgent(request, adapter) {
|
|
|
164
181
|
tokenUsageSource: parsedUsage.tokenUsageSource,
|
|
165
182
|
tokenUsageReliability: parsedUsage.tokenUsageReliability,
|
|
166
183
|
warnings: parsedUsage.warnings,
|
|
167
|
-
errors: measured.ok ? [] : [measured.error ?? "Agent command failed."]
|
|
184
|
+
errors: [...(measured.ok ? [] : [measured.error ?? "Agent command failed."]), ...(condition === "limit" ? ["Provider rate limit reached."] : condition === "timeout" ? ["Provider command timed out."] : [])]
|
|
168
185
|
};
|
|
169
186
|
}
|
|
187
|
+
function trustedProcessCondition(stdout, stderr, timedOut) {
|
|
188
|
+
if (timedOut)
|
|
189
|
+
return "timeout";
|
|
190
|
+
const diagnostics = [];
|
|
191
|
+
const isPatch = (text) => /^(?:diff --git |@@ |\+\+\+ |--- )/m.test(text);
|
|
192
|
+
// Non-interactive CLI diagnostic lines, not arbitrary substrings or diff additions.
|
|
193
|
+
if (!isPatch(stderr))
|
|
194
|
+
diagnostics.push(...stderr.split(/\r?\n/).filter(line => /^(?:error:\s*)?(?:rate limit (?:exceeded|reached)|usage limit (?:exceeded|reached)|quota (?:exceeded|exhausted)|too many requests|you(?:'ve| have) (?:hit|reached) (?:your |the )?(?:usage |session |rate )?limit)/i.test(line.trim())));
|
|
195
|
+
for (const line of stdout.split(/\r?\n/).slice(0, 1000)) {
|
|
196
|
+
try {
|
|
197
|
+
const event = JSON.parse(line);
|
|
198
|
+
if (!event || typeof event !== "object")
|
|
199
|
+
continue;
|
|
200
|
+
if (event.type !== "error" && event.type !== "turn.failed" && event.is_error !== true)
|
|
201
|
+
continue;
|
|
202
|
+
const error = event.error;
|
|
203
|
+
const message = typeof error === "string" ? error : error && typeof error === "object" ? error.message : event.message;
|
|
204
|
+
if (typeof message === "string" && message.length <= 2048 && !isPatch(message))
|
|
205
|
+
diagnostics.push(message);
|
|
206
|
+
}
|
|
207
|
+
catch { /* Not a structured error event; agent output is deliberately not diagnostic evidence. */ }
|
|
208
|
+
}
|
|
209
|
+
return diagnostics.some(text => /\b(?:rate\s+limit|usage\s+limit|quota|too many requests|session\s+limit)\b/i.test(text)) ? "limit" : null;
|
|
210
|
+
}
|
package/dist/src/agents/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from "./types.js";
|
|
|
2
2
|
export * from "./agentRegistry.js";
|
|
3
3
|
export * from "./runAgentPrompt.js";
|
|
4
4
|
export * from "./parseAgentTokenUsage.js";
|
|
5
|
+
export { normalizeAgentTokenUsageV091, normalizeAgentDurationV091 } from "./normalizeAgentTelemetry.js";
|
|
5
6
|
export * from "./adapters/fakeAgentAdapter.js";
|
|
6
7
|
export * from "./adapters/codexAdapter.js";
|
|
7
8
|
export * from "./adapters/claudeAdapter.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const EVIDENCE_LEVEL = {
|
|
2
|
+
"provider-reported": "provider-reported",
|
|
3
|
+
"cli-json": "cli-reported",
|
|
4
|
+
"agent-reported": "parsed-from-output",
|
|
5
|
+
"estimated-from-text": "estimated",
|
|
6
|
+
otel: "unavailable",
|
|
7
|
+
unavailable: "unavailable"
|
|
8
|
+
};
|
|
9
|
+
function normalizeCount(value) {
|
|
10
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0 ? value : null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Classifies the legacy evidence channel, not the origin of each individual field.
|
|
14
|
+
* Parser-derived totals are preserved; missing totals are never calculated here.
|
|
15
|
+
* The existing contract has no verified OTel attribution, so its channel remains unavailable.
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeAgentTokenUsageV091(usage, reliability) {
|
|
18
|
+
const counts = {
|
|
19
|
+
totalTokens: normalizeCount(usage.totalTokens),
|
|
20
|
+
inputTokens: normalizeCount(usage.inputTokens),
|
|
21
|
+
outputTokens: normalizeCount(usage.outputTokens),
|
|
22
|
+
cachedInputTokens: normalizeCount(usage.cachedInputTokens),
|
|
23
|
+
reasoningTokens: normalizeCount(usage.reasoningTokens)
|
|
24
|
+
};
|
|
25
|
+
const numericReasons = {};
|
|
26
|
+
for (const key of Object.keys(counts)) {
|
|
27
|
+
numericReasons[key] = counts[key] !== null ? null : usage[key] == null ? "missing-token-count" : "invalid-token-count";
|
|
28
|
+
}
|
|
29
|
+
const hasCount = Object.values(counts).some((count) => count !== null);
|
|
30
|
+
return {
|
|
31
|
+
source: usage.source,
|
|
32
|
+
reliability,
|
|
33
|
+
evidenceLevel: EVIDENCE_LEVEL[usage.source],
|
|
34
|
+
classificationReason: usage.source === "otel" ? "unattributed-source" : usage.source === "unavailable" ? "source-unavailable" : null,
|
|
35
|
+
numericAvailability: hasCount ? "available" : "unavailable",
|
|
36
|
+
condition: hasCount ? "token-available" : "token-unavailable",
|
|
37
|
+
...counts,
|
|
38
|
+
numericReasons
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Accepts the current Lab wall-clock measurement, never provider-reported timing. */
|
|
42
|
+
export function normalizeAgentDurationV091(durationMs) {
|
|
43
|
+
if (durationMs == null) {
|
|
44
|
+
return { durationMs: null, source: "unavailable", reason: "missing-duration" };
|
|
45
|
+
}
|
|
46
|
+
if (!Number.isFinite(durationMs) || durationMs < 0) {
|
|
47
|
+
return { durationMs: null, source: "unavailable", reason: "invalid-duration" };
|
|
48
|
+
}
|
|
49
|
+
return { durationMs, source: "lab-wall-clock", reason: null };
|
|
50
|
+
}
|
|
@@ -13,11 +13,11 @@ const numberFields = {
|
|
|
13
13
|
};
|
|
14
14
|
export function parseAgentTokenUsage(text) {
|
|
15
15
|
const warnings = [];
|
|
16
|
-
const jsonUsage = parseJsonUsage(text);
|
|
16
|
+
const jsonUsage = parseJsonUsage(text, warnings);
|
|
17
17
|
if (jsonUsage.found) {
|
|
18
18
|
return buildResult(jsonUsage.usage, "cli-json", text, warnings);
|
|
19
19
|
}
|
|
20
|
-
const plainTextUsage = parsePlainTextUsage(text);
|
|
20
|
+
const plainTextUsage = jsonUsage.structured ? { found: false, usage: {} } : parsePlainTextUsage(text);
|
|
21
21
|
if (plainTextUsage.found) {
|
|
22
22
|
return buildResult(plainTextUsage.usage, "agent-reported", text, warnings);
|
|
23
23
|
}
|
|
@@ -29,57 +29,84 @@ export function parseAgentTokenUsage(text) {
|
|
|
29
29
|
warnings
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
function parseJsonUsage(text) {
|
|
33
|
-
|
|
32
|
+
function parseJsonUsage(text, warnings) {
|
|
33
|
+
let usage;
|
|
34
|
+
let structured = false;
|
|
35
|
+
let visited = 0;
|
|
36
|
+
const warn = (message) => { if (!warnings.includes(message))
|
|
37
|
+
warnings.push(message); };
|
|
38
|
+
const visit = (value, depth) => {
|
|
39
|
+
if (!value || typeof value !== "object")
|
|
40
|
+
return;
|
|
41
|
+
if (++visited > 2048 || depth > 16) {
|
|
42
|
+
warn("Token usage traversal limit reached; additional envelopes were ignored.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
for (const child of value) {
|
|
47
|
+
if (visited > 2048)
|
|
48
|
+
break;
|
|
49
|
+
visit(child, depth + 1);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const record = value;
|
|
54
|
+
const local = {};
|
|
55
|
+
for (const [key, aliases] of Object.entries(numberFields)) {
|
|
56
|
+
for (const alias of aliases) {
|
|
57
|
+
const count = record[alias];
|
|
58
|
+
if (count === undefined)
|
|
59
|
+
continue;
|
|
60
|
+
if (typeof count !== "number" || !Number.isFinite(count) || !Number.isInteger(count) || count < 0) {
|
|
61
|
+
warn("Invalid token count ignored in agent output.");
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (local[key] !== undefined && local[key] !== count)
|
|
65
|
+
warn("Conflicting token aliases; legacy maximum precedence retained within one usage record.");
|
|
66
|
+
local[key] = Math.max(local[key] ?? 0, count);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (hasAnyUsage(local)) {
|
|
70
|
+
if (usage === undefined)
|
|
71
|
+
usage = local;
|
|
72
|
+
else if (JSON.stringify(usage) !== JSON.stringify(local))
|
|
73
|
+
warn("Multiple token usage records; first valid record retained without combining attempts.");
|
|
74
|
+
}
|
|
75
|
+
// Supported envelope traversal only: patch/tool payload objects are not usage metadata.
|
|
76
|
+
for (const key of ["usage", "token_usage", "tokenUsage", "response", "message", "data", "event", "result"]) {
|
|
77
|
+
if (visited > 2048)
|
|
78
|
+
break;
|
|
79
|
+
visit(record[key], depth + 1);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
let candidates = 0;
|
|
34
83
|
for (const candidate of collectJsonCandidates(text)) {
|
|
84
|
+
if (++candidates > 4096) {
|
|
85
|
+
warn("Token usage candidate limit reached; additional lines were ignored.");
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
35
88
|
try {
|
|
36
|
-
|
|
89
|
+
const value = JSON.parse(candidate);
|
|
90
|
+
structured = true;
|
|
91
|
+
visit(value, 0);
|
|
37
92
|
}
|
|
38
93
|
catch {
|
|
39
94
|
// Agent output is often mixed JSONL plus text; malformed lines are ignored.
|
|
40
95
|
}
|
|
41
96
|
}
|
|
42
|
-
return { found:
|
|
97
|
+
return { found: usage !== undefined, usage: usage ?? {}, structured };
|
|
43
98
|
}
|
|
44
|
-
function collectJsonCandidates(text) {
|
|
99
|
+
function* collectJsonCandidates(text) {
|
|
45
100
|
const trimmed = text.trim();
|
|
46
|
-
const candidates = [];
|
|
47
101
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
48
|
-
|
|
102
|
+
yield trimmed;
|
|
49
103
|
}
|
|
50
|
-
for (const
|
|
51
|
-
const lineTrimmed =
|
|
52
|
-
if (lineTrimmed.startsWith("{") || lineTrimmed.startsWith("[")) {
|
|
53
|
-
|
|
104
|
+
for (const match of text.matchAll(/[^\r\n]+/g)) {
|
|
105
|
+
const lineTrimmed = match[0].trim();
|
|
106
|
+
if (lineTrimmed !== trimmed && (lineTrimmed.startsWith("{") || lineTrimmed.startsWith("["))) {
|
|
107
|
+
yield lineTrimmed;
|
|
54
108
|
}
|
|
55
109
|
}
|
|
56
|
-
return [...new Set(candidates)];
|
|
57
|
-
}
|
|
58
|
-
function extractUsage(value) {
|
|
59
|
-
const usage = {};
|
|
60
|
-
visit(value, usage);
|
|
61
|
-
return usage;
|
|
62
|
-
}
|
|
63
|
-
function visit(value, usage) {
|
|
64
|
-
if (!value || typeof value !== "object") {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (Array.isArray(value)) {
|
|
68
|
-
value.forEach((item) => visit(item, usage));
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const record = value;
|
|
72
|
-
for (const [usageKey, fieldNames] of Object.entries(numberFields)) {
|
|
73
|
-
for (const fieldName of fieldNames) {
|
|
74
|
-
const fieldValue = record[fieldName];
|
|
75
|
-
if (typeof fieldValue === "number" && Number.isFinite(fieldValue)) {
|
|
76
|
-
usage[usageKey] = Math.max(usage[usageKey] ?? 0, fieldValue);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
for (const child of Object.values(record)) {
|
|
81
|
-
visit(child, usage);
|
|
82
|
-
}
|
|
83
110
|
}
|
|
84
111
|
function parsePlainTextUsage(text) {
|
|
85
112
|
const usage = {};
|
|
@@ -91,20 +118,15 @@ function parsePlainTextUsage(text) {
|
|
|
91
118
|
["totalTokens", /total\s+tokens?\s*[:=]\s*(\d+)/i]
|
|
92
119
|
];
|
|
93
120
|
for (const [key, pattern] of patterns) {
|
|
94
|
-
const match = text.match(pattern);
|
|
121
|
+
const match = text.match(new RegExp(pattern.source.replace("(\\d+)", "(\\d+)(?![\\d.eE])"), pattern.flags));
|
|
95
122
|
if (match?.[1]) {
|
|
96
|
-
|
|
123
|
+
const count = Number(match[1]);
|
|
124
|
+
if (Number.isFinite(count) && Number.isInteger(count))
|
|
125
|
+
usage[key] = count;
|
|
97
126
|
}
|
|
98
127
|
}
|
|
99
128
|
return { found: hasAnyUsage(usage), usage };
|
|
100
129
|
}
|
|
101
|
-
function mergeUsage(target, source) {
|
|
102
|
-
for (const key of Object.keys(numberFields)) {
|
|
103
|
-
if (source[key] !== undefined) {
|
|
104
|
-
target[key] = Math.max(target[key] ?? 0, source[key]);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
130
|
function hasAnyUsage(usage) {
|
|
109
131
|
return Object.values(usage).some((value) => typeof value === "number");
|
|
110
132
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, mkdtemp, readFile, realpath, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { resolveCommand } from "../core/resolveCommand.js";
|
|
6
|
+
import { runMeasuredCommand } from "../core/runMeasuredCommand.js";
|
|
7
|
+
const launches = new WeakMap();
|
|
8
|
+
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
9
|
+
const fail = () => { throw new Error("PROVIDER_BUILD_UNVERIFIABLE: use a native executable or an attributable node entrypoint; unsupported wrappers cannot authorize a campaign."); };
|
|
10
|
+
async function inspect(alias) {
|
|
11
|
+
const actual = await realpath(alias);
|
|
12
|
+
const stat = await lstat(actual);
|
|
13
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024 * 1024 || stat.size === 0)
|
|
14
|
+
return fail();
|
|
15
|
+
const bytes = await readFile(actual);
|
|
16
|
+
if (bytes.length !== stat.size || await realpath(alias) !== actual)
|
|
17
|
+
return fail();
|
|
18
|
+
return { proof: { alias: path.resolve(alias), actual, digest: hash(bytes) }, bytes };
|
|
19
|
+
}
|
|
20
|
+
function native(bytes) {
|
|
21
|
+
return bytes.subarray(0, 2).toString() === "MZ" || bytes.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])) || ["feedface", "feedfacf", "cefaedfe", "cffaedfe", "cafebabe"].includes(bytes.subarray(0, 4).toString("hex"));
|
|
22
|
+
}
|
|
23
|
+
/** Read-only task-free observation. All temporary version diagnostics are removed. No shell templates. */
|
|
24
|
+
export async function observeProviderBuild(providerId, options = {}) {
|
|
25
|
+
if (providerId !== "codex" && providerId !== "claude")
|
|
26
|
+
return fail();
|
|
27
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "lab-provider-build-"));
|
|
28
|
+
try {
|
|
29
|
+
const env = { ...process.env, ...options.env };
|
|
30
|
+
const resolved = resolveCommand(providerId, { cwd, env, allowPowerShellShim: false });
|
|
31
|
+
if (!resolved.resolvedPath || resolved.resolutionKind === "unavailable")
|
|
32
|
+
return fail();
|
|
33
|
+
const selected = await inspect(resolved.resolvedPath);
|
|
34
|
+
let executable = selected.proof.actual;
|
|
35
|
+
const prefix = [];
|
|
36
|
+
const files = [selected.proof];
|
|
37
|
+
let limitation = "Observed launch bytes only; transitive dependencies are not attested.";
|
|
38
|
+
if (!native(selected.bytes)) {
|
|
39
|
+
const text = selected.bytes.toString("utf8");
|
|
40
|
+
// A deliberately closed, attributable wrapper subset. Never execute arbitrary wrapper text.
|
|
41
|
+
const cmd = /^@echo off\r?\n(?:node|"([^"\r\n]+)") "([^"\r\n]+)" %\*\r?\n?$/i.exec(text);
|
|
42
|
+
const shebang = /^#!\/usr\/bin\/env node\r?\n/.test(text);
|
|
43
|
+
if (!cmd && !shebang)
|
|
44
|
+
return fail();
|
|
45
|
+
const node = resolveCommand(cmd?.[1] ?? "node", { cwd, env, allowPowerShellShim: false });
|
|
46
|
+
if (!node.resolvedPath || node.resolutionKind === "unavailable")
|
|
47
|
+
return fail();
|
|
48
|
+
const interpreter = await inspect(node.resolvedPath);
|
|
49
|
+
if (!native(interpreter.bytes))
|
|
50
|
+
return fail();
|
|
51
|
+
files.push(interpreter.proof);
|
|
52
|
+
executable = interpreter.proof.actual;
|
|
53
|
+
if (cmd) {
|
|
54
|
+
if (!path.isAbsolute(cmd[2]))
|
|
55
|
+
return fail();
|
|
56
|
+
const entry = await inspect(cmd[2]);
|
|
57
|
+
files.push(entry.proof);
|
|
58
|
+
prefix.push(entry.proof.actual);
|
|
59
|
+
}
|
|
60
|
+
else
|
|
61
|
+
prefix.push(selected.proof.actual);
|
|
62
|
+
limitation = "Restricted node wrapper, interpreter and entrypoint bytes observed; transitive dependencies are not attested.";
|
|
63
|
+
}
|
|
64
|
+
const version = await runMeasuredCommand({ commandId: `${providerId}-build-version`, executable, args: [...prefix, "--version"], resolveCommand: false, cwd, outDir: cwd, env, timeoutMs: 5000, stdoutMaxBytes: 4096, stderrMaxBytes: 4096, terminateProcessTree: true });
|
|
65
|
+
const observedVersion = version.stdout.trim();
|
|
66
|
+
if (!version.ok || version.timedOut || version.outputLimit || !/^[\x20-\x7e]{1,512}$/.test(observedVersion) || !/\d+\.\d+/.test(observedVersion))
|
|
67
|
+
return fail();
|
|
68
|
+
const capability = Object.freeze({ providerId });
|
|
69
|
+
const bytes = Buffer.from(JSON.stringify({ contract: "local-provider-launch-v1", providerId, origin: "inspected-local-executable", executable, prefix, files, version: observedVersion, adapterContract: `${providerId}-fixed-stdin-v1`, limitation }));
|
|
70
|
+
launches.set(capability, { executable, prefix, files, bytes });
|
|
71
|
+
await trustedProviderObservation(capability, providerId);
|
|
72
|
+
return capability;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return fail();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await rm(cwd, { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function trustedProviderObservation(capability, providerId) {
|
|
82
|
+
const launch = launches.get(capability);
|
|
83
|
+
if (!launch || capability.providerId !== providerId)
|
|
84
|
+
return fail();
|
|
85
|
+
for (const file of launch.files) {
|
|
86
|
+
const current = await inspect(file.alias);
|
|
87
|
+
if (current.proof.actual !== file.actual || current.proof.digest !== file.digest)
|
|
88
|
+
throw new Error("PROVIDER_BUILD_CHANGED");
|
|
89
|
+
}
|
|
90
|
+
return Buffer.from(launch.bytes);
|
|
91
|
+
}
|
|
92
|
+
/** Fixed adapter args are appended by the adapter owner; no PATH lookup occurs on this launch. */
|
|
93
|
+
export async function trustedProviderLaunch(capability, providerId) {
|
|
94
|
+
await trustedProviderObservation(capability, providerId);
|
|
95
|
+
const launch = launches.get(capability);
|
|
96
|
+
return { executable: launch.executable, prefix: [...launch.prefix] };
|
|
97
|
+
}
|
package/dist/src/cli/help.js
CHANGED
|
@@ -306,11 +306,16 @@ export function renderExperimentRunHelp() {
|
|
|
306
306
|
" with fixed-category feedback and the untrusted previous diff; it",
|
|
307
307
|
" never follows a provider, sandbox or verification infrastructure",
|
|
308
308
|
" problem.",
|
|
309
|
-
"
|
|
309
|
+
" Ordinary noncampaign execution writes report.json, report.html and report.txt plus",
|
|
310
310
|
" agent-success-rate-execution.json and",
|
|
311
311
|
" agent-success-rate-analysis.json. Initial-attempt and final success",
|
|
312
312
|
" are reported separately. Results are descriptive for the finite",
|
|
313
|
-
" corpus: no winner, ranking, composite score or significance claim."
|
|
313
|
+
" corpus: no winner, ranking, composite score or significance claim.",
|
|
314
|
+
"",
|
|
315
|
+
"agent-success-rate resumable campaigns and timeouts only:",
|
|
316
|
+
...AGENT_CAMPAIGN_OPTION_HELP.map(line => ` ${line}`),
|
|
317
|
+
"Examples (replace bracketed values; explicit source-root is required when package source proof is unavailable):",
|
|
318
|
+
...AGENT_CAMPAIGN_EXAMPLES.map(line => ` ${line}`)
|
|
314
319
|
].join("\n");
|
|
315
320
|
}
|
|
316
321
|
export function renderExperimentControlledHelp() {
|
|
@@ -527,3 +532,4 @@ export function renderTutorialRunHelp() {
|
|
|
527
532
|
" 2 Invalid command usage"
|
|
528
533
|
].join("\n");
|
|
529
534
|
}
|
|
535
|
+
import { AGENT_CAMPAIGN_EXAMPLES, AGENT_CAMPAIGN_OPTION_HELP } from "../commands/runExperimentDescribeCommand.js";
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import { createDefaultExperimentPluginRegistry } from "../experiments/index.js";
|
|
2
2
|
import { AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS, AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS, AGENT_SUCCESS_RATE_REAL_AGENT_IDS, AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES, AGENT_SUCCESS_RATE_TREATMENT_IDS } from "../experiments/plugins/agentSuccessRate/index.js";
|
|
3
|
+
import { AGENT_TIMEOUT_PRESETS } from "../experiments/plugins/agentSuccessRate/timeoutPresets.js";
|
|
4
|
+
/** Public command-owned operational contract, shared by human, JSON and run help. */
|
|
5
|
+
export const AGENT_CAMPAIGN_OPTION_HELP = [
|
|
6
|
+
"--agent-campaign <new|resume|continue-limit>: explicit version 2 campaign; requires stable --out <directory>.",
|
|
7
|
+
"--one-case: campaign-only; process at most the first unfinished selected case, with both treatments mandatory.",
|
|
8
|
+
"--campaign-source-root <directory>: campaign-only verified current source checkout; required when installed source proof is unavailable.",
|
|
9
|
+
"--timeout-preset <standard|extended|maximum>: codex/claude real-agent mode only; 240000/900000/1800000 ms per attempt.",
|
|
10
|
+
"--timeout-ms <ms>: numeric override; mutually exclusive with --timeout-preset. Neither supplied preserves 240000 ms default.",
|
|
11
|
+
"--expected-checkpoint-revision <positive-integer>: continue-limit only; exact committed checkpoint generation.",
|
|
12
|
+
"--limit-case <case-id> --limit-treatment <raw-full-file|context-pack> --limit-epoch <epoch-id>: continue-limit only; exact terminal limit unit and epoch.",
|
|
13
|
+
"--authorize-additional-invocation: mandatory boolean with no value for continue-limit; at most two execution epochs per treatment.",
|
|
14
|
+
"Resume revalidates identity, ownership and artifact digests and skips completed work, including completed task failures.",
|
|
15
|
+
"Real campaigns require --agent <codex|claude> and --include-real-agents plus trusted local build observation bound to invocation.",
|
|
16
|
+
"Native executables and attributable restricted node entrypoints are supported; unsupported wrappers are UNVERIFIABLE. No universal installation support or transitive attestation.",
|
|
17
|
+
"Uncertain invocations block resume; no force replay, marker stealing, automatic quota waiting, polling or provider switching.",
|
|
18
|
+
"Partial campaign work is persisted without final scientific reports; complete campaigns write the existing execution/analysis v1 artifacts.",
|
|
19
|
+
"Campaign exits: 0 consistent bounded work (including one-case partial), 1 paused/blocked/failed execution, 2 invalid CLI usage. Legacy behavior is preserved.",
|
|
20
|
+
"--campaign belongs to warm-index-reuse and is rejected by agent-success-rate; other plugins reject these campaign options."
|
|
21
|
+
];
|
|
22
|
+
export const AGENT_CAMPAIGN_EXAMPLES = [
|
|
23
|
+
"my-dev-kit-lab experiment run --experiment agent-success-rate --agent-campaign new --one-case --out <campaign-output-root> --campaign-source-root <source-checkout>",
|
|
24
|
+
"my-dev-kit-lab experiment run --experiment agent-success-rate --agent-campaign resume --one-case --out <same-campaign-output-root> --campaign-source-root <source-checkout>",
|
|
25
|
+
"my-dev-kit-lab experiment run --experiment agent-success-rate --agent codex --include-real-agents --agent-campaign new --timeout-preset extended --out <campaign-output-root> --campaign-source-root <source-checkout>",
|
|
26
|
+
"my-dev-kit-lab experiment run --experiment agent-success-rate --agent codex --include-real-agents --agent-campaign continue-limit --out <campaign-output-root> --campaign-source-root <source-checkout> --expected-checkpoint-revision <revision> --limit-case <case-id> --limit-treatment <treatment> --limit-epoch <epoch-id> --authorize-additional-invocation"
|
|
27
|
+
];
|
|
3
28
|
// ---------------------------------------------------------------------------
|
|
4
29
|
// v0.4.6 Batch 4 -- reusable experiment-describe command owner.
|
|
5
30
|
//
|
|
@@ -72,6 +97,8 @@ function describeAgentSuccessRate() {
|
|
|
72
97
|
realAgentMode: "Opt-in only: --agent <codex|claude> together with --include-real-agents. Neither flag alone enables it; real execution is never the default.",
|
|
73
98
|
providers: [...AGENT_SUCCESS_RATE_REAL_AGENT_IDS],
|
|
74
99
|
timeoutMs: { default: AGENT_SUCCESS_RATE_DEFAULT_AGENT_TIMEOUT_MS, maximum: AGENT_SUCCESS_RATE_MAX_AGENT_TIMEOUT_MS, scope: "each provider attempt" },
|
|
100
|
+
timeoutPresets: AGENT_TIMEOUT_PRESETS,
|
|
101
|
+
campaign: { modes: ["new", "resume", "continue-limit"], options: [...AGENT_CAMPAIGN_OPTION_HELP], examples: [...AGENT_CAMPAIGN_EXAMPLES] },
|
|
75
102
|
repairAttempts: { allowed: [...AGENT_SUCCESS_RATE_REPAIR_ATTEMPT_VALUES], default: 0, maximumTotalAttemptsPerTreatment: 3, mode: "real-agent only" },
|
|
76
103
|
agentContext: "Source-only: the provider sees the public task text and the treatment's source context, never tests, trusted checks or reference patches, and runs in a neutral working directory.",
|
|
77
104
|
patchApplication: "Guarded: the provider returns a unified diff only. The Lab validates it against a patch policy and applies it in a fresh disposable sandbox; canonical benchmark projects are never modified.",
|
|
@@ -115,6 +142,8 @@ function printDescription(plugin) {
|
|
|
115
142
|
console.log(` Real-agent mode: ${agentSuccessRate.realAgentMode}`);
|
|
116
143
|
console.log(` Providers: ${agentSuccessRate.providers.join(", ")}`);
|
|
117
144
|
console.log(` Timeout per attempt: default ${agentSuccessRate.timeoutMs.default} ms, maximum ${agentSuccessRate.timeoutMs.maximum} ms`);
|
|
145
|
+
for (const option of agentSuccessRate.campaign.options)
|
|
146
|
+
console.log(` ${option}`);
|
|
118
147
|
console.log(` Repair attempts: ${agentSuccessRate.repairAttempts.allowed.join(", ")} (default ${agentSuccessRate.repairAttempts.default}; at most ${agentSuccessRate.repairAttempts.maximumTotalAttemptsPerTreatment} attempts per treatment; real-agent mode only)`);
|
|
119
148
|
console.log(` Agent context: ${agentSuccessRate.agentContext}`);
|
|
120
149
|
console.log(` Patch application: ${agentSuccessRate.patchApplication}`);
|
|
@@ -212,6 +241,8 @@ function describeExamples(plugin) {
|
|
|
212
241
|
`my-dev-kit-lab experiment describe --experiment ${plugin.metadata.id}`,
|
|
213
242
|
`my-dev-kit-lab experiment run --experiment ${plugin.metadata.id}${agentOptions}`
|
|
214
243
|
];
|
|
244
|
+
if (plugin.metadata.id === AGENT_SUCCESS_RATE_ID)
|
|
245
|
+
examples.push(...AGENT_CAMPAIGN_EXAMPLES);
|
|
215
246
|
// Only advertise an explicit --target example for plugins that support external targets.
|
|
216
247
|
if (plugin.metadata.supportedTargets.includes("external-local") &&
|
|
217
248
|
plugin.metadata.id !== "context-window-scaling" &&
|