@prismatic-io/lux 0.0.2-preview.14 → 0.0.2-preview.16
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/lib/answerers/claude-code/index.d.ts +14 -2
- package/lib/answerers/claude-code/index.d.ts.map +1 -1
- package/lib/answerers/claude-code/index.js +3 -3
- package/lib/answerers/claude-code/index.js.map +1 -1
- package/lib/answerers/persona/index.d.ts +28 -2
- package/lib/answerers/persona/index.d.ts.map +1 -1
- package/lib/answerers/persona/index.js +3 -3
- package/lib/answerers/persona/index.js.map +1 -1
- package/lib/answerers/terminal/index.d.ts +8 -2
- package/lib/answerers/terminal/index.d.ts.map +1 -1
- package/lib/answerers/terminal/index.js +3 -2
- package/lib/answerers/terminal/index.js.map +1 -1
- package/lib/assertions/core/resource-checks.d.ts.map +1 -1
- package/lib/assertions/core/resource-checks.js +3 -5
- package/lib/assertions/core/resource-checks.js.map +1 -1
- package/lib/assertions/rubric/index.d.ts.map +1 -1
- package/lib/assertions/rubric/index.js +7 -1
- package/lib/assertions/rubric/index.js.map +1 -1
- package/lib/assertions/rubric/internal.d.ts +16 -5
- package/lib/assertions/rubric/internal.d.ts.map +1 -1
- package/lib/assertions/rubric/internal.js +10 -6
- package/lib/assertions/rubric/internal.js.map +1 -1
- package/lib/authoring.d.ts +32 -2
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js +33 -1
- package/lib/authoring.js.map +1 -1
- package/lib/cli/bin.js +2 -13
- package/lib/cli/bin.js.map +1 -1
- package/lib/cli/command-runtime.d.ts +3 -3
- package/lib/cli/command-runtime.d.ts.map +1 -1
- package/lib/cli/command-runtime.js +17 -6
- package/lib/cli/command-runtime.js.map +1 -1
- package/lib/cli/init-templates.d.ts +11 -0
- package/lib/cli/init-templates.d.ts.map +1 -0
- package/lib/cli/init-templates.js +207 -0
- package/lib/cli/init-templates.js.map +1 -0
- package/lib/cli/init.d.ts +7 -0
- package/lib/cli/init.d.ts.map +1 -1
- package/lib/cli/init.js +23 -173
- package/lib/cli/init.js.map +1 -1
- package/lib/cli/output-schemas.d.ts +1399 -0
- package/lib/cli/output-schemas.d.ts.map +1 -0
- package/lib/cli/output-schemas.js +91 -0
- package/lib/cli/output-schemas.js.map +1 -0
- package/lib/cli/program.d.ts +135 -4
- package/lib/cli/program.d.ts.map +1 -1
- package/lib/cli/program.js +725 -520
- package/lib/cli/program.js.map +1 -1
- package/lib/cli/render/reporter.d.ts.map +1 -1
- package/lib/cli/render/reporter.js +24 -4
- package/lib/cli/render/reporter.js.map +1 -1
- package/lib/cli/run-options.d.ts +0 -4
- package/lib/cli/run-options.d.ts.map +1 -1
- package/lib/cli/run-options.js +8 -19
- package/lib/cli/run-options.js.map +1 -1
- package/lib/cli/view.d.ts +5 -3
- package/lib/cli/view.d.ts.map +1 -1
- package/lib/cli/view.js +120 -78
- package/lib/cli/view.js.map +1 -1
- package/lib/core/annotation.d.ts +6 -2
- package/lib/core/annotation.d.ts.map +1 -1
- package/lib/core/annotation.js +2 -1
- package/lib/core/annotation.js.map +1 -1
- package/lib/core/answerer.d.ts +12 -0
- package/lib/core/answerer.d.ts.map +1 -1
- package/lib/core/answerer.js +2 -0
- package/lib/core/answerer.js.map +1 -1
- package/lib/core/assertion.d.ts +3 -0
- package/lib/core/assertion.d.ts.map +1 -1
- package/lib/core/assertion.js +3 -0
- package/lib/core/assertion.js.map +1 -1
- package/lib/core/case.d.ts +24 -2
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +5 -1
- package/lib/core/case.js.map +1 -1
- package/lib/core/driver.d.ts +40 -0
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js +12 -0
- package/lib/core/driver.js.map +1 -1
- package/lib/core/environment.d.ts +18 -0
- package/lib/core/environment.d.ts.map +1 -0
- package/lib/core/environment.js +21 -0
- package/lib/core/environment.js.map +1 -0
- package/lib/core/evaluation-clusters.d.ts +17 -0
- package/lib/core/evaluation-clusters.d.ts.map +1 -0
- package/lib/core/evaluation-clusters.js +23 -0
- package/lib/core/evaluation-clusters.js.map +1 -0
- package/lib/core/experiment.d.ts +11 -0
- package/lib/core/experiment.d.ts.map +1 -1
- package/lib/core/experiment.js +31 -17
- package/lib/core/experiment.js.map +1 -1
- package/lib/core/file-tree.d.ts +7 -0
- package/lib/core/file-tree.d.ts.map +1 -0
- package/lib/core/file-tree.js +35 -0
- package/lib/core/file-tree.js.map +1 -0
- package/lib/core/index.d.ts +1 -0
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/index.js +1 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/lifecycle-fixtures.d.ts.map +1 -1
- package/lib/core/lifecycle-fixtures.js +4 -2
- package/lib/core/lifecycle-fixtures.js.map +1 -1
- package/lib/core/platform-process.d.ts +5 -1
- package/lib/core/platform-process.d.ts.map +1 -1
- package/lib/core/platform-process.js +48 -10
- package/lib/core/platform-process.js.map +1 -1
- package/lib/core/run.d.ts +93 -1
- package/lib/core/run.d.ts.map +1 -1
- package/lib/core/run.js +16 -0
- package/lib/core/run.js.map +1 -1
- package/lib/core/usage.d.ts +5 -0
- package/lib/core/usage.d.ts.map +1 -1
- package/lib/core/usage.js +3 -0
- package/lib/core/usage.js.map +1 -1
- package/lib/drivers/claude-code/index.d.ts +65 -2
- package/lib/drivers/claude-code/index.d.ts.map +1 -1
- package/lib/drivers/claude-code/index.js +30 -3
- package/lib/drivers/claude-code/index.js.map +1 -1
- package/lib/drivers/codex/app-events.d.ts.map +1 -1
- package/lib/drivers/codex/app-events.js +41 -30
- package/lib/drivers/codex/app-events.js.map +1 -1
- package/lib/drivers/codex/exec-events.d.ts +4 -0
- package/lib/drivers/codex/exec-events.d.ts.map +1 -0
- package/lib/drivers/codex/exec-events.js +230 -0
- package/lib/drivers/codex/exec-events.js.map +1 -0
- package/lib/drivers/codex/index.d.ts +76 -4
- package/lib/drivers/codex/index.d.ts.map +1 -1
- package/lib/drivers/codex/index.js +35 -232
- package/lib/drivers/codex/index.js.map +1 -1
- package/lib/drivers/cursor/acp-transport.d.ts +38 -0
- package/lib/drivers/cursor/acp-transport.d.ts.map +1 -0
- package/lib/drivers/cursor/acp-transport.js +152 -0
- package/lib/drivers/cursor/acp-transport.js.map +1 -0
- package/lib/drivers/cursor/config.d.ts +22 -0
- package/lib/drivers/cursor/config.d.ts.map +1 -0
- package/lib/drivers/cursor/config.js +26 -0
- package/lib/drivers/cursor/config.js.map +1 -0
- package/lib/drivers/cursor/events.d.ts +13 -0
- package/lib/drivers/cursor/events.d.ts.map +1 -0
- package/lib/drivers/cursor/events.js +73 -0
- package/lib/drivers/cursor/events.js.map +1 -0
- package/lib/drivers/cursor/index.d.ts +37 -0
- package/lib/drivers/cursor/index.d.ts.map +1 -0
- package/lib/drivers/cursor/index.js +276 -0
- package/lib/drivers/cursor/index.js.map +1 -0
- package/lib/drivers/cursor/interaction.d.ts +7 -0
- package/lib/drivers/cursor/interaction.d.ts.map +1 -0
- package/lib/drivers/cursor/interaction.js +66 -0
- package/lib/drivers/cursor/interaction.js.map +1 -0
- package/lib/drivers/mcp/index.d.ts +35 -2
- package/lib/drivers/mcp/index.d.ts.map +1 -1
- package/lib/drivers/mcp/index.js +16 -3
- package/lib/drivers/mcp/index.js.map +1 -1
- package/lib/drivers/shared/experiment-behavior.d.ts +13 -0
- package/lib/drivers/shared/experiment-behavior.d.ts.map +1 -0
- package/lib/drivers/shared/experiment-behavior.js +197 -0
- package/lib/drivers/shared/experiment-behavior.js.map +1 -0
- package/lib/drivers/shared/experiment-policy.d.ts +4 -0
- package/lib/drivers/shared/experiment-policy.d.ts.map +1 -0
- package/lib/drivers/shared/experiment-policy.js +143 -0
- package/lib/drivers/shared/experiment-policy.js.map +1 -0
- package/lib/drivers/subprocess/index.d.ts +37 -2
- package/lib/drivers/subprocess/index.d.ts.map +1 -1
- package/lib/drivers/subprocess/index.js +46 -6
- package/lib/drivers/subprocess/index.js.map +1 -1
- package/lib/index.d.ts +6 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -3
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/annotation-loader.d.ts +6 -2
- package/lib/orchestrator/annotation-loader.d.ts.map +1 -1
- package/lib/orchestrator/annotation-loader.js +8 -0
- package/lib/orchestrator/annotation-loader.js.map +1 -1
- package/lib/orchestrator/annotation-store.d.ts.map +1 -1
- package/lib/orchestrator/annotation-store.js +16 -2
- package/lib/orchestrator/annotation-store.js.map +1 -1
- package/lib/orchestrator/campaign-lifecycle.d.ts +1 -0
- package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/campaign-lifecycle.js +4 -1
- package/lib/orchestrator/campaign-lifecycle.js.map +1 -1
- package/lib/orchestrator/compare.d.ts +4 -0
- package/lib/orchestrator/compare.d.ts.map +1 -1
- package/lib/orchestrator/compare.js +13 -1
- package/lib/orchestrator/compare.js.map +1 -1
- package/lib/orchestrator/comparison-identity.d.ts.map +1 -1
- package/lib/orchestrator/comparison-identity.js +1 -0
- package/lib/orchestrator/comparison-identity.js.map +1 -1
- package/lib/orchestrator/config.d.ts +42 -0
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +6 -2
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/discover.d.ts +1 -0
- package/lib/orchestrator/discover.d.ts.map +1 -1
- package/lib/orchestrator/discover.js +7 -1
- package/lib/orchestrator/discover.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +34 -26
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/driver-capabilities.d.ts +3 -0
- package/lib/orchestrator/driver-capabilities.d.ts.map +1 -0
- package/lib/orchestrator/driver-capabilities.js +10 -0
- package/lib/orchestrator/driver-capabilities.js.map +1 -0
- package/lib/orchestrator/driver-identity.d.ts +5 -0
- package/lib/orchestrator/driver-identity.d.ts.map +1 -0
- package/lib/orchestrator/driver-identity.js +41 -0
- package/lib/orchestrator/driver-identity.js.map +1 -0
- package/lib/orchestrator/experiment-context.d.ts +10 -0
- package/lib/orchestrator/experiment-context.d.ts.map +1 -1
- package/lib/orchestrator/experiment-contracts.d.ts +10 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.d.ts.map +1 -1
- package/lib/orchestrator/experiment-corpus.js +17 -19
- package/lib/orchestrator/experiment-corpus.js.map +1 -1
- package/lib/orchestrator/experiment-effects.d.ts +33 -0
- package/lib/orchestrator/experiment-effects.d.ts.map +1 -0
- package/lib/orchestrator/experiment-effects.js +44 -0
- package/lib/orchestrator/experiment-effects.js.map +1 -0
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -1
- package/lib/orchestrator/experiment-evaluation.js +36 -3
- package/lib/orchestrator/experiment-evaluation.js.map +1 -1
- package/lib/orchestrator/experiment-identity.d.ts.map +1 -1
- package/lib/orchestrator/experiment-identity.js +18 -29
- package/lib/orchestrator/experiment-identity.js.map +1 -1
- package/lib/orchestrator/experiment-loader.d.ts +7 -2
- package/lib/orchestrator/experiment-loader.d.ts.map +1 -1
- package/lib/orchestrator/experiment-report.d.ts +70 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -1
- package/lib/orchestrator/experiment-report.js +9 -1
- package/lib/orchestrator/experiment-report.js.map +1 -1
- package/lib/orchestrator/experiment-runs.d.ts +22 -1
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runs.js +13 -5
- package/lib/orchestrator/experiment-runs.js.map +1 -1
- package/lib/orchestrator/experiment-runtime-identity.d.ts +0 -16
- package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -1
- package/lib/orchestrator/experiment-runtime-identity.js +5 -220
- package/lib/orchestrator/experiment-runtime-identity.js.map +1 -1
- package/lib/orchestrator/experiment-selection.d.ts.map +1 -1
- package/lib/orchestrator/experiment-selection.js +52 -0
- package/lib/orchestrator/experiment-selection.js.map +1 -1
- package/lib/orchestrator/experiment.d.ts +0 -2
- package/lib/orchestrator/experiment.d.ts.map +1 -1
- package/lib/orchestrator/experiment.js +23 -15
- package/lib/orchestrator/experiment.js.map +1 -1
- package/lib/orchestrator/fs-read.d.ts +1 -6
- package/lib/orchestrator/fs-read.d.ts.map +1 -1
- package/lib/orchestrator/fs-read.js +2 -32
- package/lib/orchestrator/fs-read.js.map +1 -1
- package/lib/orchestrator/grader-experiment.d.ts +17 -1
- package/lib/orchestrator/grader-experiment.d.ts.map +1 -1
- package/lib/orchestrator/grader-experiment.js +52 -7
- package/lib/orchestrator/grader-experiment.js.map +1 -1
- package/lib/orchestrator/grading.d.ts.map +1 -1
- package/lib/orchestrator/grading.js +13 -6
- package/lib/orchestrator/grading.js.map +1 -1
- package/lib/orchestrator/list-runs.d.ts +1 -0
- package/lib/orchestrator/list-runs.d.ts.map +1 -1
- package/lib/orchestrator/list-runs.js +27 -6
- package/lib/orchestrator/list-runs.js.map +1 -1
- package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/optimization-lifecycle.js +3 -1
- package/lib/orchestrator/optimization-lifecycle.js.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts +6 -1
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +17 -143
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/lib/orchestrator/paired-effects.d.ts +34 -0
- package/lib/orchestrator/paired-effects.d.ts.map +1 -0
- package/lib/orchestrator/paired-effects.js +158 -0
- package/lib/orchestrator/paired-effects.js.map +1 -0
- package/lib/orchestrator/provenance.d.ts +5 -1
- package/lib/orchestrator/provenance.d.ts.map +1 -1
- package/lib/orchestrator/provenance.js +16 -3
- package/lib/orchestrator/provenance.js.map +1 -1
- package/lib/orchestrator/regrade.d.ts +1 -0
- package/lib/orchestrator/regrade.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.d.ts +2 -0
- package/lib/orchestrator/run-execution.d.ts.map +1 -1
- package/lib/orchestrator/run-execution.js +18 -3
- package/lib/orchestrator/run-execution.js.map +1 -1
- package/lib/orchestrator/run-lifecycle.d.ts +1 -0
- package/lib/orchestrator/run-lifecycle.d.ts.map +1 -1
- package/lib/orchestrator/run-lifecycle.js +3 -2
- package/lib/orchestrator/run-lifecycle.js.map +1 -1
- package/lib/orchestrator/suite-runs.d.ts +3 -1
- package/lib/orchestrator/suite-runs.d.ts.map +1 -1
- package/lib/orchestrator/suite-runs.js +12 -2
- package/lib/orchestrator/suite-runs.js.map +1 -1
- package/lib/orchestrator/suite-summary.d.ts.map +1 -1
- package/lib/orchestrator/suite-summary.js +2 -1
- package/lib/orchestrator/suite-summary.js.map +1 -1
- package/lib/previewer/server.d.ts +6 -0
- package/lib/previewer/server.d.ts.map +1 -1
- package/lib/previewer/server.js +21 -5
- package/lib/previewer/server.js.map +1 -1
- package/package.json +2 -2
- package/skills/lux/references/cli.md +5 -2
- package/skills/lux/references/eval-authoring.md +0 -7
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/answerers/claude-code/index.ts +3 -3
- package/src/answerers/persona/index.ts +3 -3
- package/src/answerers/terminal/index.ts +4 -9
- package/src/assertions/core/resource-checks.ts +3 -5
- package/src/assertions/rubric/index.ts +7 -1
- package/src/assertions/rubric/internal.ts +13 -8
- package/src/authoring.ts +331 -2
- package/src/cli/bin.ts +2 -12
- package/src/cli/command-runtime.ts +20 -9
- package/src/cli/init-templates.ts +223 -0
- package/src/cli/init.ts +38 -184
- package/src/cli/output-schemas.ts +106 -0
- package/src/cli/program.ts +621 -548
- package/src/cli/render/reporter.ts +25 -4
- package/src/cli/run-options.ts +8 -25
- package/src/cli/view.ts +45 -84
- package/src/core/annotation.ts +2 -1
- package/src/core/answerer.ts +9 -0
- package/src/core/assertion.ts +3 -0
- package/src/core/case.ts +5 -1
- package/src/core/driver.ts +38 -0
- package/src/core/environment.ts +22 -0
- package/src/core/evaluation-clusters.ts +35 -0
- package/src/core/experiment.ts +60 -23
- package/src/core/file-tree.ts +35 -0
- package/src/core/index.ts +1 -0
- package/src/core/lifecycle-fixtures.ts +4 -2
- package/src/core/platform-process.ts +54 -6
- package/src/core/run.ts +20 -0
- package/src/core/usage.ts +8 -0
- package/src/drivers/claude-code/index.ts +34 -3
- package/src/drivers/codex/app-events.ts +43 -33
- package/src/drivers/codex/exec-events.ts +258 -0
- package/src/drivers/codex/index.ts +37 -262
- package/src/drivers/cursor/README.md +57 -0
- package/src/drivers/cursor/acp-transport.ts +182 -0
- package/src/drivers/cursor/config.ts +29 -0
- package/src/drivers/cursor/events.ts +83 -0
- package/src/drivers/cursor/index.ts +316 -0
- package/src/drivers/cursor/interaction.ts +90 -0
- package/src/drivers/mcp/index.ts +16 -3
- package/src/drivers/shared/experiment-behavior.ts +214 -0
- package/src/drivers/shared/experiment-policy.ts +195 -0
- package/src/drivers/subprocess/index.ts +45 -6
- package/src/index.ts +16 -5
- package/src/orchestrator/annotation-loader.ts +10 -0
- package/src/orchestrator/annotation-store.ts +24 -3
- package/src/orchestrator/campaign-lifecycle.ts +5 -1
- package/src/orchestrator/compare.ts +16 -1
- package/src/orchestrator/comparison-identity.ts +1 -0
- package/src/orchestrator/config.ts +6 -2
- package/src/orchestrator/discover.ts +8 -1
- package/src/orchestrator/doctor.ts +40 -22
- package/src/orchestrator/driver-capabilities.ts +16 -0
- package/src/orchestrator/driver-identity.ts +59 -0
- package/src/orchestrator/experiment-corpus.ts +23 -28
- package/src/orchestrator/experiment-effects.ts +61 -0
- package/src/orchestrator/experiment-evaluation.ts +45 -5
- package/src/orchestrator/experiment-identity.ts +27 -36
- package/src/orchestrator/experiment-report.ts +21 -1
- package/src/orchestrator/experiment-runs.ts +11 -4
- package/src/orchestrator/experiment-runtime-identity.ts +5 -243
- package/src/orchestrator/experiment-selection.ts +77 -0
- package/src/orchestrator/experiment.ts +26 -24
- package/src/orchestrator/fs-read.ts +2 -32
- package/src/orchestrator/grader-experiment.ts +72 -8
- package/src/orchestrator/grading.ts +15 -6
- package/src/orchestrator/list-runs.ts +29 -6
- package/src/orchestrator/optimization-lifecycle.ts +4 -1
- package/src/orchestrator/orchestrator.ts +26 -183
- package/src/orchestrator/paired-effects.ts +209 -0
- package/src/orchestrator/provenance.ts +23 -3
- package/src/orchestrator/run-execution.ts +18 -3
- package/src/orchestrator/run-lifecycle.ts +4 -2
- package/src/orchestrator/suite-runs.ts +10 -1
- package/src/orchestrator/suite-summary.ts +2 -1
- package/src/previewer/server.ts +27 -5
- package/viewer/app.js +27 -6
- package/viewer/detail.js +22 -5
- package/lib/answerers/scripted/index.d.ts +0 -18
- package/lib/answerers/scripted/index.d.ts.map +0 -1
- package/lib/answerers/scripted/index.js +0 -63
- package/lib/answerers/scripted/index.js.map +0 -1
- package/lib/cli/skills.d.ts +0 -38
- package/lib/cli/skills.d.ts.map +0 -1
- package/lib/cli/skills.js +0 -84
- package/lib/cli/skills.js.map +0 -1
- package/src/answerers/scripted/index.ts +0 -78
- package/src/cli/skills.ts +0 -129
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { walkAuthoredFiles } from "../../core/file-tree.js";
|
|
6
|
+
import { contentHash, valueHash } from "../../core/hash.js";
|
|
7
|
+
import {
|
|
8
|
+
prepareClaudeExperimentConfig,
|
|
9
|
+
prepareCodexExperimentConfig,
|
|
10
|
+
} from "./experiment-policy.js";
|
|
11
|
+
|
|
12
|
+
const ClaudeSettingsFileSchema = z.looseObject({
|
|
13
|
+
enabledPlugins: z.record(z.string(), z.boolean()).optional(),
|
|
14
|
+
});
|
|
15
|
+
const ClaudeInstalledPluginRecordSchema = z.looseObject({
|
|
16
|
+
installPath: z.string(),
|
|
17
|
+
});
|
|
18
|
+
const ClaudeInstalledPluginsFileSchema = z.looseObject({
|
|
19
|
+
plugins: z.record(z.string(), z.array(ClaudeInstalledPluginRecordSchema)),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const optionalRegularFileHash = async (path: string): Promise<string | null> => {
|
|
23
|
+
try {
|
|
24
|
+
const info = await lstat(path);
|
|
25
|
+
if (info.isSymbolicLink() || !info.isFile()) {
|
|
26
|
+
throw new Error(`behavior-affecting settings must be a regular file: ${path}`);
|
|
27
|
+
}
|
|
28
|
+
return contentHash(await readFile(path));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const driverConfigRecord = (driver: unknown): Record<string, unknown> => {
|
|
36
|
+
if (!driver || typeof driver !== "object") return {};
|
|
37
|
+
const config = (driver as { config?: unknown }).config;
|
|
38
|
+
return config && typeof config === "object" ? (config as Record<string, unknown>) : {};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const claudeConfigRoot = (driver: unknown): string => {
|
|
42
|
+
const config = driverConfigRecord(driver);
|
|
43
|
+
const env =
|
|
44
|
+
config.env && typeof config.env === "object"
|
|
45
|
+
? (config.env as Record<string, unknown>)
|
|
46
|
+
: undefined;
|
|
47
|
+
const configured = env?.CLAUDE_CONFIG_DIR;
|
|
48
|
+
const root =
|
|
49
|
+
typeof configured === "string"
|
|
50
|
+
? configured
|
|
51
|
+
: (process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"));
|
|
52
|
+
if (!isAbsolute(root)) {
|
|
53
|
+
throw new Error("CLAUDE_CONFIG_DIR must be absolute for reproducible experiment identity");
|
|
54
|
+
}
|
|
55
|
+
return resolve(root);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const hashBehaviorDirectory = async (
|
|
59
|
+
root: string,
|
|
60
|
+
label: string,
|
|
61
|
+
): Promise<Array<{ path: string; hash: string }>> => {
|
|
62
|
+
try {
|
|
63
|
+
const info = await lstat(root);
|
|
64
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
65
|
+
throw new Error(`behavior-affecting Claude source must be a real directory: ${root}`);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
const paths = await walkAuthoredFiles(root);
|
|
72
|
+
if (paths.length > 5_000) {
|
|
73
|
+
throw new Error(`behavior-affecting Claude source '${label}' exceeds 5000 files`);
|
|
74
|
+
}
|
|
75
|
+
let bytes = 0;
|
|
76
|
+
const hashes: Array<{ path: string; hash: string }> = [];
|
|
77
|
+
for (const path of paths) {
|
|
78
|
+
const content = await readFile(join(root, path));
|
|
79
|
+
bytes += content.length;
|
|
80
|
+
if (bytes > 256 * 1024 * 1024) {
|
|
81
|
+
throw new Error(`behavior-affecting Claude source '${label}' exceeds 256 MiB`);
|
|
82
|
+
}
|
|
83
|
+
hashes.push({ path: `${label}/${path}`, hash: contentHash(content) });
|
|
84
|
+
}
|
|
85
|
+
return hashes;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Hash every intentionally loaded user behavior source without persisting its
|
|
90
|
+
* contents. This covers user instructions/rules/output styles and the exact
|
|
91
|
+
* installed contents of plugins enabled by settings.json.
|
|
92
|
+
*/
|
|
93
|
+
export const claudeUserBehaviorHash = async (configRoot: string): Promise<string | null> => {
|
|
94
|
+
const settingsPath = join(configRoot, "settings.json");
|
|
95
|
+
const settingsHash = await optionalRegularFileHash(settingsPath);
|
|
96
|
+
const sources: Array<{ path: string; hash: string }> = [];
|
|
97
|
+
if (settingsHash) sources.push({ path: "settings.json", hash: settingsHash });
|
|
98
|
+
const claudeMdHash = await optionalRegularFileHash(join(configRoot, "CLAUDE.md"));
|
|
99
|
+
if (claudeMdHash) sources.push({ path: "CLAUDE.md", hash: claudeMdHash });
|
|
100
|
+
for (const directory of ["rules", "output-styles", "agents", "commands", "skills"]) {
|
|
101
|
+
sources.push(...(await hashBehaviorDirectory(join(configRoot, directory), directory)));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let settings: unknown;
|
|
105
|
+
try {
|
|
106
|
+
const contents = await readFile(settingsPath, "utf8");
|
|
107
|
+
try {
|
|
108
|
+
settings = JSON.parse(contents);
|
|
109
|
+
} catch {
|
|
110
|
+
throw new Error("Claude settings.json contains invalid JSON");
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
114
|
+
settings = undefined;
|
|
115
|
+
} else {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const parsedSettings = ClaudeSettingsFileSchema.safeParse(settings);
|
|
120
|
+
if (settings !== undefined && !parsedSettings.success) {
|
|
121
|
+
throw new Error("Claude settings.json has an invalid enabledPlugins map");
|
|
122
|
+
}
|
|
123
|
+
const enabledPlugins = parsedSettings.success ? parsedSettings.data.enabledPlugins : undefined;
|
|
124
|
+
const enabledIds = Object.entries(enabledPlugins ?? {})
|
|
125
|
+
.filter(([, value]) => value === true)
|
|
126
|
+
.map(([id]) => id)
|
|
127
|
+
.sort();
|
|
128
|
+
if (enabledIds.length > 0) {
|
|
129
|
+
const installedPath = join(configRoot, "plugins", "installed_plugins.json");
|
|
130
|
+
const installedHash = await optionalRegularFileHash(installedPath);
|
|
131
|
+
if (installedHash) {
|
|
132
|
+
sources.push({ path: "plugins/installed_plugins.json", hash: installedHash });
|
|
133
|
+
}
|
|
134
|
+
let installed: unknown;
|
|
135
|
+
try {
|
|
136
|
+
installed = JSON.parse(await readFile(installedPath, "utf8"));
|
|
137
|
+
} catch {
|
|
138
|
+
installed = undefined;
|
|
139
|
+
}
|
|
140
|
+
const parsedInstalled = ClaudeInstalledPluginsFileSchema.safeParse(installed);
|
|
141
|
+
if (!parsedInstalled.success) {
|
|
142
|
+
throw new Error("enabled Claude plugins have invalid installed metadata");
|
|
143
|
+
}
|
|
144
|
+
const plugins = parsedInstalled.data.plugins;
|
|
145
|
+
for (const id of enabledIds) {
|
|
146
|
+
const records = plugins[id];
|
|
147
|
+
if (!records || records.length === 0) {
|
|
148
|
+
throw new Error(`enabled Claude plugin '${id}' has no installed content to hash`);
|
|
149
|
+
}
|
|
150
|
+
for (const [index, record] of records.entries()) {
|
|
151
|
+
const installPath = record.installPath;
|
|
152
|
+
sources.push(
|
|
153
|
+
...(await hashBehaviorDirectory(resolve(installPath), `plugins/enabled/${id}/${index}`)),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return sources.length === 0 ? null : valueHash(sources);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const configuredClaudeSourceHashes = async (
|
|
162
|
+
driver: unknown,
|
|
163
|
+
baseRoot: string,
|
|
164
|
+
cache: Map<string, Promise<string>> = new Map(),
|
|
165
|
+
): Promise<Array<{ kind: "plugin" | "read"; path: string; hash: string }>> => {
|
|
166
|
+
const name =
|
|
167
|
+
typeof driver === "string" ? driver : (driver as { name?: unknown } | undefined)?.name;
|
|
168
|
+
const driverCli = name === "claude-code" || name === "codex" ? { name } : undefined;
|
|
169
|
+
if (!driverCli) return [];
|
|
170
|
+
const config = driverConfigRecord(driver);
|
|
171
|
+
// This also rejects path-bearing extraArgs that would evade the source
|
|
172
|
+
// hashes below. Runtime applies the same guard before driver creation.
|
|
173
|
+
if (driverCli.name === "claude-code") prepareClaudeExperimentConfig(config);
|
|
174
|
+
else prepareCodexExperimentConfig(config);
|
|
175
|
+
const isolation =
|
|
176
|
+
config.isolation && typeof config.isolation === "object"
|
|
177
|
+
? (config.isolation as Record<string, unknown>)
|
|
178
|
+
: undefined;
|
|
179
|
+
const configured = [
|
|
180
|
+
...(driverCli.name === "claude-code" && Array.isArray(config.pluginDirs)
|
|
181
|
+
? config.pluginDirs.map((path) => ({ kind: "plugin" as const, path }))
|
|
182
|
+
: []),
|
|
183
|
+
...(Array.isArray(config.addDirs)
|
|
184
|
+
? config.addDirs.map((path) => ({ kind: "read" as const, path }))
|
|
185
|
+
: []),
|
|
186
|
+
...(driverCli.name === "claude-code" && isolation && Array.isArray(isolation.allowRead)
|
|
187
|
+
? isolation.allowRead.map((path) => ({ kind: "read" as const, path }))
|
|
188
|
+
: []),
|
|
189
|
+
].filter(
|
|
190
|
+
(entry): entry is { kind: "plugin" | "read"; path: string } =>
|
|
191
|
+
typeof entry.path === "string" && entry.path.length > 0,
|
|
192
|
+
);
|
|
193
|
+
return Promise.all(
|
|
194
|
+
configured
|
|
195
|
+
.sort((a, b) => `${a.kind}:${a.path}`.localeCompare(`${b.kind}:${b.path}`))
|
|
196
|
+
.map(async ({ kind, path }) => {
|
|
197
|
+
const absolute = resolve(baseRoot, path);
|
|
198
|
+
const key = `${kind}:${absolute}`;
|
|
199
|
+
let hash = cache.get(key);
|
|
200
|
+
if (!hash) {
|
|
201
|
+
hash = hashBehaviorDirectory(
|
|
202
|
+
absolute,
|
|
203
|
+
`driver/${kind}/${contentHash(absolute).slice(0, 12)}`,
|
|
204
|
+
).then(valueHash);
|
|
205
|
+
cache.set(key, hash);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
kind,
|
|
209
|
+
path: absolute,
|
|
210
|
+
hash: await hash,
|
|
211
|
+
};
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
|
|
3
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
4
|
+
Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
|
|
6
|
+
const EXPERIMENT_BEHAVIOR_SOURCE_ARGS = new Set([
|
|
7
|
+
"--continue",
|
|
8
|
+
"-c",
|
|
9
|
+
"--fork-session",
|
|
10
|
+
"--resume",
|
|
11
|
+
"-r",
|
|
12
|
+
"--add-dir",
|
|
13
|
+
"--append-system-prompt-file",
|
|
14
|
+
"--mcp-config",
|
|
15
|
+
"--plugin-dir",
|
|
16
|
+
"--settings",
|
|
17
|
+
"--setting-sources",
|
|
18
|
+
"--system-prompt-file",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const CODEX_EXPERIMENT_SOURCE_ARGS = new Set([
|
|
22
|
+
"--add-dir",
|
|
23
|
+
"--cd",
|
|
24
|
+
"-C",
|
|
25
|
+
"--config",
|
|
26
|
+
"-c",
|
|
27
|
+
"--profile",
|
|
28
|
+
"-p",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const bindSubprocessExperimentConfig = (config: Record<string, unknown>): unknown => {
|
|
32
|
+
if (typeof config.command !== "string" || !isAbsolute(config.command)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"experiment subprocess commands must be absolute direct wrappers so Lux can fingerprint the invoked behavior",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (typeof config.cwd === "string") {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"experiment runs do not allow an explicit subprocess cwd; use the candidate subject and staged fixtures",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(config.args) && config.args.length > 0) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"experiment subprocesses do not allow args; put stable behavior in the fingerprinted direct wrapper",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (isRecord(config.env) && Object.keys(config.env).length > 0) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"experiment subprocesses do not allow custom env; put stable behavior in the fingerprinted direct wrapper",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return { ...config, inheritEnv: false };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type AgentDriverName = "claude-code" | "codex";
|
|
56
|
+
|
|
57
|
+
const validateAgentExperimentConfig = (
|
|
58
|
+
driverName: AgentDriverName,
|
|
59
|
+
config: Record<string, unknown>,
|
|
60
|
+
): { driverEnv: Record<string, unknown>; extraArgs: string[] } => {
|
|
61
|
+
const driverEnv = isRecord(config.env) ? config.env : {};
|
|
62
|
+
const commandResolutionOverride = Object.keys(driverEnv).find((name) =>
|
|
63
|
+
["PATH", "PATHEXT"].includes(name.toUpperCase()),
|
|
64
|
+
);
|
|
65
|
+
if (commandResolutionOverride) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`experiment runs do not allow driver env.${commandResolutionOverride}; command resolution must match the fingerprinted host executable`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (typeof config.cwd === "string") {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`experiment runs do not allow an explicit ${driverName} cwd; use the candidate subject and staged fixtures`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const extraArgs = Array.isArray(config.extraArgs)
|
|
77
|
+
? config.extraArgs.filter((value): value is string => typeof value === "string")
|
|
78
|
+
: [];
|
|
79
|
+
const disallowedArgs =
|
|
80
|
+
driverName === "claude-code" ? EXPERIMENT_BEHAVIOR_SOURCE_ARGS : CODEX_EXPERIMENT_SOURCE_ARGS;
|
|
81
|
+
const behaviorSourceArg = extraArgs.find((arg) =>
|
|
82
|
+
[...disallowedArgs].some((name) => arg === name || arg.startsWith(`${name}=`)),
|
|
83
|
+
);
|
|
84
|
+
if (behaviorSourceArg) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`experiment runs do not allow behavior-bearing extraArgs '${behaviorSourceArg}'; use structured driver fields so Lux can fingerprint the source`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const isolation = isRecord(config.isolation) ? config.isolation : {};
|
|
91
|
+
const structuredPaths = [
|
|
92
|
+
...(driverName === "claude-code" && Array.isArray(config.pluginDirs) ? config.pluginDirs : []),
|
|
93
|
+
...(Array.isArray(config.addDirs) ? config.addDirs : []),
|
|
94
|
+
...(driverName === "claude-code" && Array.isArray(isolation.allowRead)
|
|
95
|
+
? isolation.allowRead
|
|
96
|
+
: []),
|
|
97
|
+
].filter((value): value is string => typeof value === "string");
|
|
98
|
+
const relativeSource = structuredPaths.find((path) => !isAbsolute(path));
|
|
99
|
+
if (relativeSource) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`experiment driver source paths must be absolute after subjectPath binding: ${relativeSource}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return { driverEnv, extraArgs };
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const bindCodexExperimentConfig = (
|
|
108
|
+
config: Record<string, unknown>,
|
|
109
|
+
extraArgs: readonly string[],
|
|
110
|
+
): unknown => {
|
|
111
|
+
if (typeof config.profile === "string") {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"experiment runs do not allow a Codex profile; use inline driver config so Lux can fingerprint it",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (config.ephemeral === false) {
|
|
117
|
+
throw new Error("experiment runs require ephemeral Codex sessions");
|
|
118
|
+
}
|
|
119
|
+
const inlineConfig = isRecord(config.config) ? config.config : {};
|
|
120
|
+
const externalConfigKey = ((): string | undefined => {
|
|
121
|
+
const visit = (value: Record<string, unknown>, parent = ""): string | undefined => {
|
|
122
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
123
|
+
const path = parent ? `${parent}.${key}` : key;
|
|
124
|
+
if (/(?:mcp_servers|instructions_file|notify|project_doc)/i.test(key)) return path;
|
|
125
|
+
if (isRecord(nested)) {
|
|
126
|
+
const found = visit(nested, path);
|
|
127
|
+
if (found) return found;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
};
|
|
132
|
+
return visit(inlineConfig);
|
|
133
|
+
})();
|
|
134
|
+
if (externalConfigKey) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`experiment runs do not allow path-bearing Codex config '${externalConfigKey}'`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const appServer = config.interactionMode !== "exec";
|
|
140
|
+
return {
|
|
141
|
+
...config,
|
|
142
|
+
ephemeral: true,
|
|
143
|
+
ignoreUserConfig: true,
|
|
144
|
+
ignoreRules: !appServer,
|
|
145
|
+
...(appServer && !config.isolation ? { isolation: { allowRead: [] } } : {}),
|
|
146
|
+
extraArgs,
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const bindClaudeExperimentConfig = (
|
|
151
|
+
config: Record<string, unknown>,
|
|
152
|
+
extraArgs: readonly string[],
|
|
153
|
+
driverEnv: Record<string, unknown>,
|
|
154
|
+
): unknown => {
|
|
155
|
+
if (config.isolation) return config;
|
|
156
|
+
return {
|
|
157
|
+
...config,
|
|
158
|
+
extraArgs: [
|
|
159
|
+
...extraArgs,
|
|
160
|
+
"--setting-sources",
|
|
161
|
+
"user",
|
|
162
|
+
"--strict-mcp-config",
|
|
163
|
+
"--settings",
|
|
164
|
+
JSON.stringify({ disableAllHooks: true }),
|
|
165
|
+
],
|
|
166
|
+
env: {
|
|
167
|
+
...driverEnv,
|
|
168
|
+
CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
|
|
169
|
+
CLAUDE_CODE_DISABLE_CLAUDE_MDS: "1",
|
|
170
|
+
CLAUDE_CODE_SKIP_PROMPT_HISTORY: "1",
|
|
171
|
+
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
|
|
172
|
+
CLAUDE_CODE_AUTO_CONNECT_IDE: "false",
|
|
173
|
+
CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL: "1",
|
|
174
|
+
DISABLE_AUTOUPDATER: "1",
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const prepareClaudeExperimentConfig = <Cfg extends Record<string, unknown>>(
|
|
180
|
+
config: Cfg,
|
|
181
|
+
): Cfg => {
|
|
182
|
+
const { driverEnv, extraArgs } = validateAgentExperimentConfig("claude-code", config);
|
|
183
|
+
return bindClaudeExperimentConfig(config, extraArgs, driverEnv) as Cfg;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const prepareCodexExperimentConfig = <Cfg extends Record<string, unknown>>(
|
|
187
|
+
config: Cfg,
|
|
188
|
+
): Cfg => {
|
|
189
|
+
const { extraArgs } = validateAgentExperimentConfig("codex", config);
|
|
190
|
+
return bindCodexExperimentConfig(config, extraArgs) as Cfg;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export const prepareSubprocessExperimentConfig = <Cfg extends Record<string, unknown>>(
|
|
194
|
+
config: Cfg,
|
|
195
|
+
): Cfg => bindSubprocessExperimentConfig(config) as Cfg;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type Artifact,
|
|
9
9
|
DoneSchema,
|
|
10
10
|
type DriverEventMap,
|
|
11
|
-
|
|
11
|
+
defineDriver,
|
|
12
12
|
ErrorEventSchema,
|
|
13
13
|
type FormatAnswerFn,
|
|
14
14
|
formatWireAnswer,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
TypedEmitter,
|
|
26
26
|
} from "../../core/index.js";
|
|
27
27
|
import { walkArtifacts } from "../shared/artifacts.js";
|
|
28
|
+
import { prepareSubprocessExperimentConfig } from "../shared/experiment-policy.js";
|
|
28
29
|
|
|
29
30
|
export const SubprocessDriverConfigSchema = z
|
|
30
31
|
.object({
|
|
@@ -81,6 +82,8 @@ export type SubprocessDriverConfig = z.infer<typeof SubprocessDriverConfigSchema
|
|
|
81
82
|
const KILL_GRACE_MS = 5_000;
|
|
82
83
|
const DEFAULT_MAX_LINE_BYTES = 10_000_000;
|
|
83
84
|
const MAX_STDERR_TAIL_CHARS = 8_192;
|
|
85
|
+
const MAX_STDERR_CHARS = 65_536;
|
|
86
|
+
const MAX_STDERR_EVENTS = 128;
|
|
84
87
|
const DEFAULT_MAX_ARTIFACT_FILES = 500;
|
|
85
88
|
const MAX_FIXTURE_VALUES_BYTES = 64 * 1024;
|
|
86
89
|
const ARTIFACT_SNAPSHOT_MODULE = fileURLToPath(
|
|
@@ -174,15 +177,38 @@ class SubprocessDriverImpl implements AgentDriver {
|
|
|
174
177
|
// boundary from being mangled.
|
|
175
178
|
child.stderr.setEncoding("utf8");
|
|
176
179
|
let stderrTail = "";
|
|
180
|
+
let stderrChars = 0;
|
|
181
|
+
let stderrEvents = 0;
|
|
182
|
+
let stderrTruncated = false;
|
|
183
|
+
const recordStderr = (line: string): void => {
|
|
184
|
+
if (stderrTruncated || this.terminalSent) return;
|
|
185
|
+
const text = line.slice(0, Math.min(MAX_STDERR_TAIL_CHARS, MAX_STDERR_CHARS - stderrChars));
|
|
186
|
+
stderrChars += text.length;
|
|
187
|
+
stderrEvents++;
|
|
188
|
+
stderrTruncated =
|
|
189
|
+
text.length < line.length ||
|
|
190
|
+
stderrChars >= MAX_STDERR_CHARS ||
|
|
191
|
+
stderrEvents >= MAX_STDERR_EVENTS;
|
|
192
|
+
this.emitInOrder("progress", {
|
|
193
|
+
id: `subprocess-stderr-${stderrEvents}`,
|
|
194
|
+
kind: "driver-stderr",
|
|
195
|
+
payload: { text, truncated: stderrTruncated },
|
|
196
|
+
ts: Date.now(),
|
|
197
|
+
});
|
|
198
|
+
};
|
|
177
199
|
child.stderr.on("data", (chunk: string) => {
|
|
178
200
|
const lines = (stderrTail + chunk).split("\n");
|
|
179
201
|
// Cap the retained partial line so unterminated stderr can't grow
|
|
180
202
|
// without bound.
|
|
181
|
-
stderrTail =
|
|
182
|
-
for (const line of lines)
|
|
203
|
+
stderrTail = lines.pop() ?? "";
|
|
204
|
+
for (const line of lines) recordStderr(line);
|
|
205
|
+
if (stderrTail.length > MAX_STDERR_TAIL_CHARS) {
|
|
206
|
+
recordStderr(stderrTail);
|
|
207
|
+
stderrTail = "";
|
|
208
|
+
}
|
|
183
209
|
});
|
|
184
210
|
child.stderr.on("close", () => {
|
|
185
|
-
if (stderrTail)
|
|
211
|
+
if (stderrTail) recordStderr(stderrTail);
|
|
186
212
|
stderrTail = "";
|
|
187
213
|
});
|
|
188
214
|
|
|
@@ -489,8 +515,21 @@ class SubprocessDriverImpl implements AgentDriver {
|
|
|
489
515
|
}
|
|
490
516
|
}
|
|
491
517
|
|
|
492
|
-
export const subprocessDriver
|
|
518
|
+
export const subprocessDriver = defineDriver({
|
|
493
519
|
name: "subprocess",
|
|
494
520
|
configSchema: SubprocessDriverConfigSchema,
|
|
521
|
+
runtime: (config) => ({
|
|
522
|
+
commands: [config.command],
|
|
523
|
+
capabilities: {
|
|
524
|
+
interactive: true,
|
|
525
|
+
artifacts: true,
|
|
526
|
+
isolation: false,
|
|
527
|
+
},
|
|
528
|
+
}),
|
|
529
|
+
experiment: {
|
|
530
|
+
version: "1",
|
|
531
|
+
prepare: prepareSubprocessExperimentConfig,
|
|
532
|
+
identity: () => null,
|
|
533
|
+
},
|
|
495
534
|
create: (config) => new SubprocessDriverImpl(config),
|
|
496
|
-
};
|
|
535
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -16,11 +16,6 @@ export {
|
|
|
16
16
|
PersonaAnswererConfigSchema,
|
|
17
17
|
personaAnswerer,
|
|
18
18
|
} from "./answerers/persona/index.js";
|
|
19
|
-
export {
|
|
20
|
-
type ScriptedAnswererConfig,
|
|
21
|
-
ScriptedAnswererConfigSchema,
|
|
22
|
-
scriptedAnswerer,
|
|
23
|
-
} from "./answerers/scripted/index.js";
|
|
24
19
|
export {
|
|
25
20
|
type TerminalAnswererConfig,
|
|
26
21
|
TerminalAnswererConfigSchema,
|
|
@@ -188,11 +183,14 @@ export {
|
|
|
188
183
|
AnswererModelCallAccountingSchema,
|
|
189
184
|
type AnswererPlugin,
|
|
190
185
|
type AnswerHistoryEntry,
|
|
186
|
+
answererRef,
|
|
187
|
+
defineAnswerer,
|
|
191
188
|
} from "./core/answerer.js";
|
|
192
189
|
export {
|
|
193
190
|
inspectRunArtifact,
|
|
194
191
|
type ReadRunArtifactTextOptions,
|
|
195
192
|
type RunArtifactInspection,
|
|
193
|
+
type RunArtifactIssueReason,
|
|
196
194
|
readRunArtifactText,
|
|
197
195
|
} from "./core/artifact-evidence.js";
|
|
198
196
|
export {
|
|
@@ -231,6 +229,7 @@ export {
|
|
|
231
229
|
type EvalCase,
|
|
232
230
|
type EvalCaseAuthoringOptions,
|
|
233
231
|
type EvalCaseDefinition,
|
|
232
|
+
type EvalCaseInput,
|
|
234
233
|
EvalCaseSchema,
|
|
235
234
|
type FixturesRef,
|
|
236
235
|
FixturesRefSchema,
|
|
@@ -251,8 +250,14 @@ export {
|
|
|
251
250
|
type AgentDriver,
|
|
252
251
|
type Artifact,
|
|
253
252
|
ArtifactSchema,
|
|
253
|
+
type DriverCapability,
|
|
254
|
+
DriverCapabilitySchema,
|
|
254
255
|
type DriverEventMap,
|
|
256
|
+
type DriverExperiment,
|
|
255
257
|
type DriverPlugin,
|
|
258
|
+
type DriverRuntime,
|
|
259
|
+
defineDriver,
|
|
260
|
+
driverRef,
|
|
256
261
|
type ResolvedFixtures,
|
|
257
262
|
ResolvedFixturesSchema,
|
|
258
263
|
type StartContext,
|
|
@@ -271,6 +276,7 @@ export {
|
|
|
271
276
|
ReportedDriverMetricsSchema,
|
|
272
277
|
} from "./core/driver-metrics.js";
|
|
273
278
|
export { type EventHandler, TypedEmitter } from "./core/emitter.js";
|
|
279
|
+
export { type EnvironmentDeclaration, EnvironmentDeclarationSchema } from "./core/environment.js";
|
|
274
280
|
export {
|
|
275
281
|
type AgentAttribution,
|
|
276
282
|
AgentAttributionSchema,
|
|
@@ -476,6 +482,11 @@ export {
|
|
|
476
482
|
CodexDriverConfigSchema,
|
|
477
483
|
codexDriver,
|
|
478
484
|
} from "./drivers/codex/index.js";
|
|
485
|
+
export {
|
|
486
|
+
type CursorDriverConfig,
|
|
487
|
+
CursorDriverConfigSchema,
|
|
488
|
+
cursorDriver,
|
|
489
|
+
} from "./drivers/cursor/index.js";
|
|
479
490
|
export {
|
|
480
491
|
type McpProbeDriverConfig,
|
|
481
492
|
McpProbeDriverConfigSchema,
|
|
@@ -2,6 +2,7 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, extname, resolve, sep } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { assertDisjointClusters } from "../core/evaluation-clusters.js";
|
|
5
6
|
import {
|
|
6
7
|
type ExperimentSelector,
|
|
7
8
|
formatZodIssues,
|
|
@@ -73,6 +74,15 @@ export const selectGraderAnnotations = (
|
|
|
73
74
|
export const assertDisjointAnnotationSplits = (
|
|
74
75
|
splits: Array<{ name: string; annotations: readonly GraderAnnotation[] }>,
|
|
75
76
|
): void => {
|
|
77
|
+
assertDisjointClusters(
|
|
78
|
+
splits.map((split) => ({
|
|
79
|
+
name: split.name,
|
|
80
|
+
items: split.annotations.map((annotation) => ({
|
|
81
|
+
id: annotation.id,
|
|
82
|
+
cluster: annotation.meta?.cluster,
|
|
83
|
+
})),
|
|
84
|
+
})),
|
|
85
|
+
);
|
|
76
86
|
const idOwners = new Map<string, string>();
|
|
77
87
|
const runOwners = new Map<string, string>();
|
|
78
88
|
for (const split of splits) {
|
|
@@ -82,11 +82,10 @@ const acquireAnnotationLock = async (path: string): Promise<() => Promise<void>>
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const updateAnnotation = async (
|
|
86
|
+
path: string,
|
|
87
87
|
annotation: GraderAnnotation,
|
|
88
88
|
): Promise<GraderAnnotationDataset> => {
|
|
89
|
-
const path = resolve(filePath);
|
|
90
89
|
await mkdir(dirname(path), { recursive: true });
|
|
91
90
|
const release = await acquireAnnotationLock(path);
|
|
92
91
|
const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
@@ -114,3 +113,25 @@ export const upsertGraderAnnotation = async (
|
|
|
114
113
|
await release();
|
|
115
114
|
}
|
|
116
115
|
};
|
|
116
|
+
|
|
117
|
+
// Serialize local writers before acquiring the cross-process lock. Otherwise
|
|
118
|
+
// a burst of updates spends its lock deadline contending with its own process.
|
|
119
|
+
const pendingUpdates = new Map<string, Promise<void>>();
|
|
120
|
+
|
|
121
|
+
export const upsertGraderAnnotation = (
|
|
122
|
+
filePath: string,
|
|
123
|
+
annotation: GraderAnnotation,
|
|
124
|
+
): Promise<GraderAnnotationDataset> => {
|
|
125
|
+
const path = resolve(filePath);
|
|
126
|
+
const previous = pendingUpdates.get(path) ?? Promise.resolve();
|
|
127
|
+
const update = previous.then(() => updateAnnotation(path, annotation));
|
|
128
|
+
const settled = update.then(
|
|
129
|
+
() => undefined,
|
|
130
|
+
() => undefined,
|
|
131
|
+
);
|
|
132
|
+
pendingUpdates.set(path, settled);
|
|
133
|
+
void settled.then(() => {
|
|
134
|
+
if (pendingUpdates.get(path) === settled) pendingUpdates.delete(path);
|
|
135
|
+
});
|
|
136
|
+
return update;
|
|
137
|
+
};
|
|
@@ -16,6 +16,7 @@ type CampaignLifecycleContext = {
|
|
|
16
16
|
work: unknown;
|
|
17
17
|
result: unknown;
|
|
18
18
|
error: unknown;
|
|
19
|
+
failed: boolean;
|
|
19
20
|
terminalStatus: "passed" | "failed" | "aborted" | undefined;
|
|
20
21
|
};
|
|
21
22
|
type CampaignLifecycleOutcome =
|
|
@@ -57,6 +58,7 @@ const campaignLifecycleMachine = setup({
|
|
|
57
58
|
"output" in event ? context.effects.resultStatus(event.output) : "failed",
|
|
58
59
|
}),
|
|
59
60
|
captureError: assign({
|
|
61
|
+
failed: true,
|
|
60
62
|
error: ({ event }) =>
|
|
61
63
|
"error" in event ? event.error : new Error("campaign lifecycle failed"),
|
|
62
64
|
terminalStatus: ({ context, event }) =>
|
|
@@ -65,6 +67,7 @@ const campaignLifecycleMachine = setup({
|
|
|
65
67
|
),
|
|
66
68
|
}),
|
|
67
69
|
captureReleaseError: assign({
|
|
70
|
+
failed: true,
|
|
68
71
|
error: ({ event }) => ("error" in event ? event.error : new Error("campaign release failed")),
|
|
69
72
|
terminalStatus: "failed",
|
|
70
73
|
}),
|
|
@@ -76,6 +79,7 @@ const campaignLifecycleMachine = setup({
|
|
|
76
79
|
work: undefined,
|
|
77
80
|
result: undefined,
|
|
78
81
|
error: undefined,
|
|
82
|
+
failed: false,
|
|
79
83
|
terminalStatus: undefined,
|
|
80
84
|
}),
|
|
81
85
|
initial: "preparing",
|
|
@@ -145,7 +149,7 @@ const campaignLifecycleMachine = setup({
|
|
|
145
149
|
failed: { type: "final" },
|
|
146
150
|
},
|
|
147
151
|
output: ({ context }) => {
|
|
148
|
-
if (context.
|
|
152
|
+
if (context.failed) {
|
|
149
153
|
return {
|
|
150
154
|
status: context.terminalStatus === "aborted" ? "aborted" : "failed",
|
|
151
155
|
error: context.error,
|