@prismatic-io/lux 0.0.2-preview.15 → 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 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -2
- 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 -0
- 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-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 -0
- 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 -0
- 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/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/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
|
@@ -183,11 +183,14 @@ export {
|
|
|
183
183
|
AnswererModelCallAccountingSchema,
|
|
184
184
|
type AnswererPlugin,
|
|
185
185
|
type AnswerHistoryEntry,
|
|
186
|
+
answererRef,
|
|
187
|
+
defineAnswerer,
|
|
186
188
|
} from "./core/answerer.js";
|
|
187
189
|
export {
|
|
188
190
|
inspectRunArtifact,
|
|
189
191
|
type ReadRunArtifactTextOptions,
|
|
190
192
|
type RunArtifactInspection,
|
|
193
|
+
type RunArtifactIssueReason,
|
|
191
194
|
readRunArtifactText,
|
|
192
195
|
} from "./core/artifact-evidence.js";
|
|
193
196
|
export {
|
|
@@ -226,6 +229,7 @@ export {
|
|
|
226
229
|
type EvalCase,
|
|
227
230
|
type EvalCaseAuthoringOptions,
|
|
228
231
|
type EvalCaseDefinition,
|
|
232
|
+
type EvalCaseInput,
|
|
229
233
|
EvalCaseSchema,
|
|
230
234
|
type FixturesRef,
|
|
231
235
|
FixturesRefSchema,
|
|
@@ -246,8 +250,14 @@ export {
|
|
|
246
250
|
type AgentDriver,
|
|
247
251
|
type Artifact,
|
|
248
252
|
ArtifactSchema,
|
|
253
|
+
type DriverCapability,
|
|
254
|
+
DriverCapabilitySchema,
|
|
249
255
|
type DriverEventMap,
|
|
256
|
+
type DriverExperiment,
|
|
250
257
|
type DriverPlugin,
|
|
258
|
+
type DriverRuntime,
|
|
259
|
+
defineDriver,
|
|
260
|
+
driverRef,
|
|
251
261
|
type ResolvedFixtures,
|
|
252
262
|
ResolvedFixturesSchema,
|
|
253
263
|
type StartContext,
|
|
@@ -266,6 +276,7 @@ export {
|
|
|
266
276
|
ReportedDriverMetricsSchema,
|
|
267
277
|
} from "./core/driver-metrics.js";
|
|
268
278
|
export { type EventHandler, TypedEmitter } from "./core/emitter.js";
|
|
279
|
+
export { type EnvironmentDeclaration, EnvironmentDeclarationSchema } from "./core/environment.js";
|
|
269
280
|
export {
|
|
270
281
|
type AgentAttribution,
|
|
271
282
|
AgentAttributionSchema,
|
|
@@ -471,6 +482,11 @@ export {
|
|
|
471
482
|
CodexDriverConfigSchema,
|
|
472
483
|
codexDriver,
|
|
473
484
|
} from "./drivers/codex/index.js";
|
|
485
|
+
export {
|
|
486
|
+
type CursorDriverConfig,
|
|
487
|
+
CursorDriverConfigSchema,
|
|
488
|
+
cursorDriver,
|
|
489
|
+
} from "./drivers/cursor/index.js";
|
|
474
490
|
export {
|
|
475
491
|
type McpProbeDriverConfig,
|
|
476
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,
|