@prismatic-io/lux 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/lib/answerers/claude-code/index.d.ts +17 -0
- package/lib/answerers/claude-code/index.d.ts.map +1 -0
- package/lib/answerers/claude-code/index.js +280 -0
- package/lib/answerers/claude-code/index.js.map +1 -0
- package/lib/answerers/persona/index.d.ts +40 -0
- package/lib/answerers/persona/index.d.ts.map +1 -0
- package/lib/answerers/persona/index.js +265 -0
- package/lib/answerers/persona/index.js.map +1 -0
- package/lib/answerers/scripted/index.d.ts +8 -0
- package/lib/answerers/scripted/index.d.ts.map +1 -0
- package/lib/answerers/scripted/index.js +163 -0
- package/lib/answerers/scripted/index.js.map +1 -0
- package/lib/answerers/shared.d.ts +13 -0
- package/lib/answerers/shared.d.ts.map +1 -0
- package/lib/answerers/shared.js +17 -0
- package/lib/answerers/shared.js.map +1 -0
- package/lib/answerers/terminal/index.d.ts +9 -0
- package/lib/answerers/terminal/index.d.ts.map +1 -0
- package/lib/answerers/terminal/index.js +97 -0
- package/lib/answerers/terminal/index.js.map +1 -0
- package/lib/assertions/core/command-exits-zero.d.ts +21 -0
- package/lib/assertions/core/command-exits-zero.d.ts.map +1 -0
- package/lib/assertions/core/command-exits-zero.js +87 -0
- package/lib/assertions/core/command-exits-zero.js.map +1 -0
- package/lib/assertions/core/event-checks.d.ts +57 -0
- package/lib/assertions/core/event-checks.d.ts.map +1 -0
- package/lib/assertions/core/event-checks.js +123 -0
- package/lib/assertions/core/event-checks.js.map +1 -0
- package/lib/assertions/core/file-checks.d.ts +103 -0
- package/lib/assertions/core/file-checks.d.ts.map +1 -0
- package/lib/assertions/core/file-checks.js +288 -0
- package/lib/assertions/core/file-checks.js.map +1 -0
- package/lib/assertions/core/helpers.d.ts +66 -0
- package/lib/assertions/core/helpers.d.ts.map +1 -0
- package/lib/assertions/core/helpers.js +170 -0
- package/lib/assertions/core/helpers.js.map +1 -0
- package/lib/assertions/core/index.d.ts +15 -0
- package/lib/assertions/core/index.d.ts.map +1 -0
- package/lib/assertions/core/index.js +56 -0
- package/lib/assertions/core/index.js.map +1 -0
- package/lib/assertions/core/json-pointer-equals.d.ts +19 -0
- package/lib/assertions/core/json-pointer-equals.d.ts.map +1 -0
- package/lib/assertions/core/json-pointer-equals.js +71 -0
- package/lib/assertions/core/json-pointer-equals.js.map +1 -0
- package/lib/assertions/core/output-checks.d.ts +46 -0
- package/lib/assertions/core/output-checks.d.ts.map +1 -0
- package/lib/assertions/core/output-checks.js +121 -0
- package/lib/assertions/core/output-checks.js.map +1 -0
- package/lib/assertions/core/predicate.d.ts +14 -0
- package/lib/assertions/core/predicate.d.ts.map +1 -0
- package/lib/assertions/core/predicate.js +55 -0
- package/lib/assertions/core/predicate.js.map +1 -0
- package/lib/assertions/core/resource-checks.d.ts +71 -0
- package/lib/assertions/core/resource-checks.d.ts.map +1 -0
- package/lib/assertions/core/resource-checks.js +159 -0
- package/lib/assertions/core/resource-checks.js.map +1 -0
- package/lib/assertions/core/safe-regex.d.ts +23 -0
- package/lib/assertions/core/safe-regex.d.ts.map +1 -0
- package/lib/assertions/core/safe-regex.js +102 -0
- package/lib/assertions/core/safe-regex.js.map +1 -0
- package/lib/assertions/core/tool-called-with.d.ts +21 -0
- package/lib/assertions/core/tool-called-with.d.ts.map +1 -0
- package/lib/assertions/core/tool-called-with.js +69 -0
- package/lib/assertions/core/tool-called-with.js.map +1 -0
- package/lib/assertions/core/tool-calls.d.ts +22 -0
- package/lib/assertions/core/tool-calls.d.ts.map +1 -0
- package/lib/assertions/core/tool-calls.js +17 -0
- package/lib/assertions/core/tool-calls.js.map +1 -0
- package/lib/assertions/core/tool-invocation.d.ts +36 -0
- package/lib/assertions/core/tool-invocation.d.ts.map +1 -0
- package/lib/assertions/core/tool-invocation.js +170 -0
- package/lib/assertions/core/tool-invocation.js.map +1 -0
- package/lib/assertions/core/tool-result-checks.d.ts +59 -0
- package/lib/assertions/core/tool-result-checks.d.ts.map +1 -0
- package/lib/assertions/core/tool-result-checks.js +193 -0
- package/lib/assertions/core/tool-result-checks.js.map +1 -0
- package/lib/assertions/mcp/discovery-checks.d.ts +36 -0
- package/lib/assertions/mcp/discovery-checks.d.ts.map +1 -0
- package/lib/assertions/mcp/discovery-checks.js +122 -0
- package/lib/assertions/mcp/discovery-checks.js.map +1 -0
- package/lib/assertions/mcp/index.d.ts +10 -0
- package/lib/assertions/mcp/index.d.ts.map +1 -0
- package/lib/assertions/mcp/index.js +25 -0
- package/lib/assertions/mcp/index.js.map +1 -0
- package/lib/assertions/rubric/index.d.ts +104 -0
- package/lib/assertions/rubric/index.d.ts.map +1 -0
- package/lib/assertions/rubric/index.js +191 -0
- package/lib/assertions/rubric/index.js.map +1 -0
- package/lib/assertions/rubric/internal.d.ts +14 -0
- package/lib/assertions/rubric/internal.d.ts.map +1 -0
- package/lib/assertions/rubric/internal.js +335 -0
- package/lib/assertions/rubric/internal.js.map +1 -0
- package/lib/authoring.d.ts +3 -0
- package/lib/authoring.d.ts.map +1 -0
- package/lib/authoring.js +3 -0
- package/lib/authoring.js.map +1 -0
- package/lib/cli/bin.d.ts +3 -0
- package/lib/cli/bin.d.ts.map +1 -0
- package/lib/cli/bin.js +15 -0
- package/lib/cli/bin.js.map +1 -0
- package/lib/cli/command-runtime.d.ts +5 -0
- package/lib/cli/command-runtime.d.ts.map +1 -0
- package/lib/cli/command-runtime.js +30 -0
- package/lib/cli/command-runtime.js.map +1 -0
- package/lib/cli/init.d.ts +21 -0
- package/lib/cli/init.d.ts.map +1 -0
- package/lib/cli/init.js +340 -0
- package/lib/cli/init.js.map +1 -0
- package/lib/cli/program.d.ts +22 -0
- package/lib/cli/program.d.ts.map +1 -0
- package/lib/cli/program.js +757 -0
- package/lib/cli/program.js.map +1 -0
- package/lib/cli/render/format.d.ts +14 -0
- package/lib/cli/render/format.d.ts.map +1 -0
- package/lib/cli/render/format.js +71 -0
- package/lib/cli/render/format.js.map +1 -0
- package/lib/cli/render/reporter.d.ts +73 -0
- package/lib/cli/render/reporter.d.ts.map +1 -0
- package/lib/cli/render/reporter.js +519 -0
- package/lib/cli/render/reporter.js.map +1 -0
- package/lib/cli/render/tty.d.ts +77 -0
- package/lib/cli/render/tty.d.ts.map +1 -0
- package/lib/cli/render/tty.js +283 -0
- package/lib/cli/render/tty.js.map +1 -0
- package/lib/cli/run-options.d.ts +27 -0
- package/lib/cli/run-options.d.ts.map +1 -0
- package/lib/cli/run-options.js +58 -0
- package/lib/cli/run-options.js.map +1 -0
- package/lib/cli/skills.d.ts +38 -0
- package/lib/cli/skills.d.ts.map +1 -0
- package/lib/cli/skills.js +84 -0
- package/lib/cli/skills.js.map +1 -0
- package/lib/core/annotation.d.ts +56 -0
- package/lib/core/annotation.d.ts.map +1 -0
- package/lib/core/annotation.js +92 -0
- package/lib/core/annotation.js.map +1 -0
- package/lib/core/answerer.d.ts +68 -0
- package/lib/core/answerer.d.ts.map +1 -0
- package/lib/core/answerer.js +17 -0
- package/lib/core/answerer.js.map +1 -0
- package/lib/core/artifact-evidence.d.ts +50 -0
- package/lib/core/artifact-evidence.d.ts.map +1 -0
- package/lib/core/artifact-evidence.js +185 -0
- package/lib/core/artifact-evidence.js.map +1 -0
- package/lib/core/artifact-snapshot.d.ts +37 -0
- package/lib/core/artifact-snapshot.d.ts.map +1 -0
- package/lib/core/artifact-snapshot.js +339 -0
- package/lib/core/artifact-snapshot.js.map +1 -0
- package/lib/core/assertion.d.ts +129 -0
- package/lib/core/assertion.d.ts.map +1 -0
- package/lib/core/assertion.js +75 -0
- package/lib/core/assertion.js.map +1 -0
- package/lib/core/case.d.ts +79 -0
- package/lib/core/case.d.ts.map +1 -0
- package/lib/core/case.js +90 -0
- package/lib/core/case.js.map +1 -0
- package/lib/core/conversation-turn.d.ts +23 -0
- package/lib/core/conversation-turn.d.ts.map +1 -0
- package/lib/core/conversation-turn.js +31 -0
- package/lib/core/conversation-turn.js.map +1 -0
- package/lib/core/driver.d.ts +64 -0
- package/lib/core/driver.d.ts.map +1 -0
- package/lib/core/driver.js +18 -0
- package/lib/core/driver.js.map +1 -0
- package/lib/core/emitter.d.ts +20 -0
- package/lib/core/emitter.d.ts.map +1 -0
- package/lib/core/emitter.js +34 -0
- package/lib/core/emitter.js.map +1 -0
- package/lib/core/events.d.ts +202 -0
- package/lib/core/events.d.ts.map +1 -0
- package/lib/core/events.js +107 -0
- package/lib/core/events.js.map +1 -0
- package/lib/core/exec.d.ts +82 -0
- package/lib/core/exec.d.ts.map +1 -0
- package/lib/core/exec.js +168 -0
- package/lib/core/exec.js.map +1 -0
- package/lib/core/experiment.d.ts +285 -0
- package/lib/core/experiment.d.ts.map +1 -0
- package/lib/core/experiment.js +389 -0
- package/lib/core/experiment.js.map +1 -0
- package/lib/core/glob.d.ts +3 -0
- package/lib/core/glob.d.ts.map +1 -0
- package/lib/core/glob.js +32 -0
- package/lib/core/glob.js.map +1 -0
- package/lib/core/hash.d.ts +8 -0
- package/lib/core/hash.d.ts.map +1 -0
- package/lib/core/hash.js +214 -0
- package/lib/core/hash.js.map +1 -0
- package/lib/core/index.d.ts +29 -0
- package/lib/core/index.d.ts.map +1 -0
- package/lib/core/index.js +29 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/lifecycle-fixtures.d.ts +20 -0
- package/lib/core/lifecycle-fixtures.d.ts.map +1 -0
- package/lib/core/lifecycle-fixtures.js +109 -0
- package/lib/core/lifecycle-fixtures.js.map +1 -0
- package/lib/core/lifecycle-hooks.d.ts +52 -0
- package/lib/core/lifecycle-hooks.d.ts.map +1 -0
- package/lib/core/lifecycle-hooks.js +42 -0
- package/lib/core/lifecycle-hooks.js.map +1 -0
- package/lib/core/mcp-events.d.ts +44 -0
- package/lib/core/mcp-events.d.ts.map +1 -0
- package/lib/core/mcp-events.js +23 -0
- package/lib/core/mcp-events.js.map +1 -0
- package/lib/core/model-cli.d.ts +57 -0
- package/lib/core/model-cli.d.ts.map +1 -0
- package/lib/core/model-cli.js +237 -0
- package/lib/core/model-cli.js.map +1 -0
- package/lib/core/observer.d.ts +31 -0
- package/lib/core/observer.d.ts.map +1 -0
- package/lib/core/observer.js +25 -0
- package/lib/core/observer.js.map +1 -0
- package/lib/core/platform-process.d.ts +33 -0
- package/lib/core/platform-process.d.ts.map +1 -0
- package/lib/core/platform-process.js +214 -0
- package/lib/core/platform-process.js.map +1 -0
- package/lib/core/registry.d.ts +22 -0
- package/lib/core/registry.d.ts.map +1 -0
- package/lib/core/registry.js +45 -0
- package/lib/core/registry.js.map +1 -0
- package/lib/core/rubric.d.ts +18 -0
- package/lib/core/rubric.d.ts.map +1 -0
- package/lib/core/rubric.js +49 -0
- package/lib/core/rubric.js.map +1 -0
- package/lib/core/run.d.ts +419 -0
- package/lib/core/run.d.ts.map +1 -0
- package/lib/core/run.js +126 -0
- package/lib/core/run.js.map +1 -0
- package/lib/core/statistics.d.ts +39 -0
- package/lib/core/statistics.d.ts.map +1 -0
- package/lib/core/statistics.js +163 -0
- package/lib/core/statistics.js.map +1 -0
- package/lib/core/subject.d.ts +4 -0
- package/lib/core/subject.d.ts.map +1 -0
- package/lib/core/subject.js +46 -0
- package/lib/core/subject.js.map +1 -0
- package/lib/core/tool-events.d.ts +69 -0
- package/lib/core/tool-events.d.ts.map +1 -0
- package/lib/core/tool-events.js +242 -0
- package/lib/core/tool-events.js.map +1 -0
- package/lib/core/usage.d.ts +78 -0
- package/lib/core/usage.d.ts.map +1 -0
- package/lib/core/usage.js +104 -0
- package/lib/core/usage.js.map +1 -0
- package/lib/core/wire.d.ts +83 -0
- package/lib/core/wire.d.ts.map +1 -0
- package/lib/core/wire.js +101 -0
- package/lib/core/wire.js.map +1 -0
- package/lib/core/zod.d.ts +10 -0
- package/lib/core/zod.d.ts.map +1 -0
- package/lib/core/zod.js +18 -0
- package/lib/core/zod.js.map +1 -0
- package/lib/drivers/claude-code/index.d.ts +55 -0
- package/lib/drivers/claude-code/index.d.ts.map +1 -0
- package/lib/drivers/claude-code/index.js +779 -0
- package/lib/drivers/claude-code/index.js.map +1 -0
- package/lib/drivers/claude-code/parse-events.d.ts +46 -0
- package/lib/drivers/claude-code/parse-events.d.ts.map +1 -0
- package/lib/drivers/claude-code/parse-events.js +387 -0
- package/lib/drivers/claude-code/parse-events.js.map +1 -0
- package/lib/drivers/codex/app-events.d.ts +16 -0
- package/lib/drivers/codex/app-events.d.ts.map +1 -0
- package/lib/drivers/codex/app-events.js +184 -0
- package/lib/drivers/codex/app-events.js.map +1 -0
- package/lib/drivers/codex/config.d.ts +55 -0
- package/lib/drivers/codex/config.d.ts.map +1 -0
- package/lib/drivers/codex/config.js +125 -0
- package/lib/drivers/codex/config.js.map +1 -0
- package/lib/drivers/codex/index.d.ts +8 -0
- package/lib/drivers/codex/index.d.ts.map +1 -0
- package/lib/drivers/codex/index.js +904 -0
- package/lib/drivers/codex/index.js.map +1 -0
- package/lib/drivers/mcp/index.d.ts +52 -0
- package/lib/drivers/mcp/index.d.ts.map +1 -0
- package/lib/drivers/mcp/index.js +282 -0
- package/lib/drivers/mcp/index.js.map +1 -0
- package/lib/drivers/mcp/transport.d.ts +50 -0
- package/lib/drivers/mcp/transport.d.ts.map +1 -0
- package/lib/drivers/mcp/transport.js +190 -0
- package/lib/drivers/mcp/transport.js.map +1 -0
- package/lib/drivers/shared/artifacts.d.ts +4 -0
- package/lib/drivers/shared/artifacts.d.ts.map +1 -0
- package/lib/drivers/shared/artifacts.js +81 -0
- package/lib/drivers/shared/artifacts.js.map +1 -0
- package/lib/drivers/shared/secure-copy.d.ts +2 -0
- package/lib/drivers/shared/secure-copy.d.ts.map +1 -0
- package/lib/drivers/shared/secure-copy.js +35 -0
- package/lib/drivers/shared/secure-copy.js.map +1 -0
- package/lib/drivers/subprocess/index.d.ts +24 -0
- package/lib/drivers/subprocess/index.d.ts.map +1 -0
- package/lib/drivers/subprocess/index.js +451 -0
- package/lib/drivers/subprocess/index.js.map +1 -0
- package/lib/index.d.ts +40 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +48 -0
- package/lib/index.js.map +1 -0
- package/lib/optimization/reflective.d.ts +27 -0
- package/lib/optimization/reflective.d.ts.map +1 -0
- package/lib/optimization/reflective.js +257 -0
- package/lib/optimization/reflective.js.map +1 -0
- package/lib/orchestrator/annotation-loader.d.ts +65 -0
- package/lib/orchestrator/annotation-loader.d.ts.map +1 -0
- package/lib/orchestrator/annotation-loader.js +73 -0
- package/lib/orchestrator/annotation-loader.js.map +1 -0
- package/lib/orchestrator/annotation-store.d.ts +3 -0
- package/lib/orchestrator/annotation-store.d.ts.map +1 -0
- package/lib/orchestrator/annotation-store.js +100 -0
- package/lib/orchestrator/annotation-store.js.map +1 -0
- package/lib/orchestrator/authored-dependencies.d.ts +3 -0
- package/lib/orchestrator/authored-dependencies.d.ts.map +1 -0
- package/lib/orchestrator/authored-dependencies.js +146 -0
- package/lib/orchestrator/authored-dependencies.js.map +1 -0
- package/lib/orchestrator/campaign-lifecycle.d.ts +105 -0
- package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -0
- package/lib/orchestrator/campaign-lifecycle.js +141 -0
- package/lib/orchestrator/campaign-lifecycle.js.map +1 -0
- package/lib/orchestrator/candidate-integrity.d.ts +4 -0
- package/lib/orchestrator/candidate-integrity.d.ts.map +1 -0
- package/lib/orchestrator/candidate-integrity.js +14 -0
- package/lib/orchestrator/candidate-integrity.js.map +1 -0
- package/lib/orchestrator/candidates.d.ts +13 -0
- package/lib/orchestrator/candidates.d.ts.map +1 -0
- package/lib/orchestrator/candidates.js +577 -0
- package/lib/orchestrator/candidates.js.map +1 -0
- package/lib/orchestrator/compare.d.ts +80 -0
- package/lib/orchestrator/compare.d.ts.map +1 -0
- package/lib/orchestrator/compare.js +270 -0
- package/lib/orchestrator/compare.js.map +1 -0
- package/lib/orchestrator/comparison-identity.d.ts +10 -0
- package/lib/orchestrator/comparison-identity.d.ts.map +1 -0
- package/lib/orchestrator/comparison-identity.js +39 -0
- package/lib/orchestrator/comparison-identity.js.map +1 -0
- package/lib/orchestrator/config.d.ts +121 -0
- package/lib/orchestrator/config.d.ts.map +1 -0
- package/lib/orchestrator/config.js +176 -0
- package/lib/orchestrator/config.js.map +1 -0
- package/lib/orchestrator/content-bound-json.d.ts +5 -0
- package/lib/orchestrator/content-bound-json.d.ts.map +1 -0
- package/lib/orchestrator/content-bound-json.js +59 -0
- package/lib/orchestrator/content-bound-json.js.map +1 -0
- package/lib/orchestrator/discover.d.ts +45 -0
- package/lib/orchestrator/discover.d.ts.map +1 -0
- package/lib/orchestrator/discover.js +98 -0
- package/lib/orchestrator/discover.js.map +1 -0
- package/lib/orchestrator/doctor.d.ts +141 -0
- package/lib/orchestrator/doctor.d.ts.map +1 -0
- package/lib/orchestrator/doctor.js +525 -0
- package/lib/orchestrator/doctor.js.map +1 -0
- package/lib/orchestrator/driver-session.d.ts +177 -0
- package/lib/orchestrator/driver-session.d.ts.map +1 -0
- package/lib/orchestrator/driver-session.js +396 -0
- package/lib/orchestrator/driver-session.js.map +1 -0
- package/lib/orchestrator/evaluator-identity.d.ts +7 -0
- package/lib/orchestrator/evaluator-identity.d.ts.map +1 -0
- package/lib/orchestrator/evaluator-identity.js +42 -0
- package/lib/orchestrator/evaluator-identity.js.map +1 -0
- package/lib/orchestrator/experiment-budget.d.ts +11 -0
- package/lib/orchestrator/experiment-budget.d.ts.map +1 -0
- package/lib/orchestrator/experiment-budget.js +51 -0
- package/lib/orchestrator/experiment-budget.js.map +1 -0
- package/lib/orchestrator/experiment-context.d.ts +43 -0
- package/lib/orchestrator/experiment-context.d.ts.map +1 -0
- package/lib/orchestrator/experiment-context.js +9 -0
- package/lib/orchestrator/experiment-context.js.map +1 -0
- package/lib/orchestrator/experiment-contracts.d.ts +290 -0
- package/lib/orchestrator/experiment-contracts.d.ts.map +1 -0
- package/lib/orchestrator/experiment-contracts.js +38 -0
- package/lib/orchestrator/experiment-contracts.js.map +1 -0
- package/lib/orchestrator/experiment-corpus.d.ts +21 -0
- package/lib/orchestrator/experiment-corpus.d.ts.map +1 -0
- package/lib/orchestrator/experiment-corpus.js +276 -0
- package/lib/orchestrator/experiment-corpus.js.map +1 -0
- package/lib/orchestrator/experiment-evaluation.d.ts +44 -0
- package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -0
- package/lib/orchestrator/experiment-evaluation.js +542 -0
- package/lib/orchestrator/experiment-evaluation.js.map +1 -0
- package/lib/orchestrator/experiment-finish.d.ts +6 -0
- package/lib/orchestrator/experiment-finish.d.ts.map +1 -0
- package/lib/orchestrator/experiment-finish.js +19 -0
- package/lib/orchestrator/experiment-finish.js.map +1 -0
- package/lib/orchestrator/experiment-identity.d.ts +6 -0
- package/lib/orchestrator/experiment-identity.d.ts.map +1 -0
- package/lib/orchestrator/experiment-identity.js +257 -0
- package/lib/orchestrator/experiment-identity.js.map +1 -0
- package/lib/orchestrator/experiment-loader.d.ts +175 -0
- package/lib/orchestrator/experiment-loader.d.ts.map +1 -0
- package/lib/orchestrator/experiment-loader.js +77 -0
- package/lib/orchestrator/experiment-loader.js.map +1 -0
- package/lib/orchestrator/experiment-report.d.ts +450 -0
- package/lib/orchestrator/experiment-report.d.ts.map +1 -0
- package/lib/orchestrator/experiment-report.js +570 -0
- package/lib/orchestrator/experiment-report.js.map +1 -0
- package/lib/orchestrator/experiment-runs.d.ts +379 -0
- package/lib/orchestrator/experiment-runs.d.ts.map +1 -0
- package/lib/orchestrator/experiment-runs.js +796 -0
- package/lib/orchestrator/experiment-runs.js.map +1 -0
- package/lib/orchestrator/experiment-runtime-identity.d.ts +41 -0
- package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -0
- package/lib/orchestrator/experiment-runtime-identity.js +411 -0
- package/lib/orchestrator/experiment-runtime-identity.js.map +1 -0
- package/lib/orchestrator/experiment-selection.d.ts +25 -0
- package/lib/orchestrator/experiment-selection.d.ts.map +1 -0
- package/lib/orchestrator/experiment-selection.js +286 -0
- package/lib/orchestrator/experiment-selection.js.map +1 -0
- package/lib/orchestrator/experiment.d.ts +11 -0
- package/lib/orchestrator/experiment.d.ts.map +1 -0
- package/lib/orchestrator/experiment.js +639 -0
- package/lib/orchestrator/experiment.js.map +1 -0
- package/lib/orchestrator/fixture-identity.d.ts +7 -0
- package/lib/orchestrator/fixture-identity.d.ts.map +1 -0
- package/lib/orchestrator/fixture-identity.js +55 -0
- package/lib/orchestrator/fixture-identity.js.map +1 -0
- package/lib/orchestrator/fs-read.d.ts +19 -0
- package/lib/orchestrator/fs-read.d.ts.map +1 -0
- package/lib/orchestrator/fs-read.js +113 -0
- package/lib/orchestrator/fs-read.js.map +1 -0
- package/lib/orchestrator/grader-experiment.d.ts +190 -0
- package/lib/orchestrator/grader-experiment.d.ts.map +1 -0
- package/lib/orchestrator/grader-experiment.js +579 -0
- package/lib/orchestrator/grader-experiment.js.map +1 -0
- package/lib/orchestrator/grading.d.ts +31 -0
- package/lib/orchestrator/grading.d.ts.map +1 -0
- package/lib/orchestrator/grading.js +186 -0
- package/lib/orchestrator/grading.js.map +1 -0
- package/lib/orchestrator/index.d.ts +22 -0
- package/lib/orchestrator/index.d.ts.map +1 -0
- package/lib/orchestrator/index.js +30 -0
- package/lib/orchestrator/index.js.map +1 -0
- package/lib/orchestrator/lifecycle-hook-execution.d.ts +4 -0
- package/lib/orchestrator/lifecycle-hook-execution.d.ts.map +1 -0
- package/lib/orchestrator/lifecycle-hook-execution.js +67 -0
- package/lib/orchestrator/lifecycle-hook-execution.js.map +1 -0
- package/lib/orchestrator/list-runs.d.ts +33 -0
- package/lib/orchestrator/list-runs.d.ts.map +1 -0
- package/lib/orchestrator/list-runs.js +308 -0
- package/lib/orchestrator/list-runs.js.map +1 -0
- package/lib/orchestrator/loader.d.ts +3 -0
- package/lib/orchestrator/loader.d.ts.map +1 -0
- package/lib/orchestrator/loader.js +17 -0
- package/lib/orchestrator/loader.js.map +1 -0
- package/lib/orchestrator/optimization-lifecycle.d.ts +12 -0
- package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -0
- package/lib/orchestrator/optimization-lifecycle.js +92 -0
- package/lib/orchestrator/optimization-lifecycle.js.map +1 -0
- package/lib/orchestrator/orchestrator.d.ts +144 -0
- package/lib/orchestrator/orchestrator.d.ts.map +1 -0
- package/lib/orchestrator/orchestrator.js +632 -0
- package/lib/orchestrator/orchestrator.js.map +1 -0
- package/lib/orchestrator/owned-lock.d.ts +17 -0
- package/lib/orchestrator/owned-lock.d.ts.map +1 -0
- package/lib/orchestrator/owned-lock.js +149 -0
- package/lib/orchestrator/owned-lock.js.map +1 -0
- package/lib/orchestrator/package-version.d.ts +2 -0
- package/lib/orchestrator/package-version.d.ts.map +1 -0
- package/lib/orchestrator/package-version.js +4 -0
- package/lib/orchestrator/package-version.js.map +1 -0
- package/lib/orchestrator/provenance.d.ts +5 -0
- package/lib/orchestrator/provenance.d.ts.map +1 -0
- package/lib/orchestrator/provenance.js +137 -0
- package/lib/orchestrator/provenance.js.map +1 -0
- package/lib/orchestrator/regrade.d.ts +80 -0
- package/lib/orchestrator/regrade.d.ts.map +1 -0
- package/lib/orchestrator/regrade.js +433 -0
- package/lib/orchestrator/regrade.js.map +1 -0
- package/lib/orchestrator/rubric-loader.d.ts +7 -0
- package/lib/orchestrator/rubric-loader.d.ts.map +1 -0
- package/lib/orchestrator/rubric-loader.js +65 -0
- package/lib/orchestrator/rubric-loader.js.map +1 -0
- package/lib/orchestrator/run-dir.d.ts +40 -0
- package/lib/orchestrator/run-dir.d.ts.map +1 -0
- package/lib/orchestrator/run-dir.js +161 -0
- package/lib/orchestrator/run-dir.js.map +1 -0
- package/lib/orchestrator/run-execution.d.ts +30 -0
- package/lib/orchestrator/run-execution.d.ts.map +1 -0
- package/lib/orchestrator/run-execution.js +531 -0
- package/lib/orchestrator/run-execution.js.map +1 -0
- package/lib/orchestrator/run-lifecycle.d.ts +115 -0
- package/lib/orchestrator/run-lifecycle.d.ts.map +1 -0
- package/lib/orchestrator/run-lifecycle.js +136 -0
- package/lib/orchestrator/run-lifecycle.js.map +1 -0
- package/lib/orchestrator/suite-compare.d.ts +76 -0
- package/lib/orchestrator/suite-compare.d.ts.map +1 -0
- package/lib/orchestrator/suite-compare.js +159 -0
- package/lib/orchestrator/suite-compare.js.map +1 -0
- package/lib/orchestrator/suite-lifecycle.d.ts +157 -0
- package/lib/orchestrator/suite-lifecycle.d.ts.map +1 -0
- package/lib/orchestrator/suite-lifecycle.js +253 -0
- package/lib/orchestrator/suite-lifecycle.js.map +1 -0
- package/lib/orchestrator/suite-runs.d.ts +91 -0
- package/lib/orchestrator/suite-runs.d.ts.map +1 -0
- package/lib/orchestrator/suite-runs.js +157 -0
- package/lib/orchestrator/suite-runs.js.map +1 -0
- package/lib/orchestrator/suite-summary.d.ts +109 -0
- package/lib/orchestrator/suite-summary.d.ts.map +1 -0
- package/lib/orchestrator/suite-summary.js +213 -0
- package/lib/orchestrator/suite-summary.js.map +1 -0
- package/lib/orchestrator/trace-limits.d.ts +20 -0
- package/lib/orchestrator/trace-limits.d.ts.map +1 -0
- package/lib/orchestrator/trace-limits.js +28 -0
- package/lib/orchestrator/trace-limits.js.map +1 -0
- package/lib/orchestrator/validate-plugin-config.d.ts +10 -0
- package/lib/orchestrator/validate-plugin-config.d.ts.map +1 -0
- package/lib/orchestrator/validate-plugin-config.js +26 -0
- package/lib/orchestrator/validate-plugin-config.js.map +1 -0
- package/package.json +84 -0
- package/skills/lux/SKILL.md +85 -0
- package/skills/lux/references/cli.md +102 -0
- package/skills/lux/references/eval-authoring.md +105 -0
- package/skills/lux-answerer/SKILL.md +169 -0
- package/src/answerers/claude-code/index.ts +342 -0
- package/src/answerers/persona/index.ts +327 -0
- package/src/answerers/scripted/index.ts +186 -0
- package/src/answerers/shared.ts +23 -0
- package/src/answerers/terminal/index.ts +117 -0
- package/src/assertions/core/command-exits-zero.ts +94 -0
- package/src/assertions/core/event-checks.ts +138 -0
- package/src/assertions/core/file-checks.ts +336 -0
- package/src/assertions/core/helpers.ts +203 -0
- package/src/assertions/core/index.ts +80 -0
- package/src/assertions/core/json-pointer-equals.ts +84 -0
- package/src/assertions/core/output-checks.ts +134 -0
- package/src/assertions/core/predicate.ts +67 -0
- package/src/assertions/core/resource-checks.ts +180 -0
- package/src/assertions/core/safe-regex.ts +196 -0
- package/src/assertions/core/tool-called-with.ts +68 -0
- package/src/assertions/core/tool-calls.ts +28 -0
- package/src/assertions/core/tool-invocation.ts +198 -0
- package/src/assertions/core/tool-result-checks.ts +211 -0
- package/src/assertions/mcp/discovery-checks.ts +139 -0
- package/src/assertions/mcp/index.ts +37 -0
- package/src/assertions/rubric/index.ts +258 -0
- package/src/assertions/rubric/internal.ts +367 -0
- package/src/authoring.ts +2 -0
- package/src/cli/bin.ts +14 -0
- package/src/cli/command-runtime.ts +34 -0
- package/src/cli/init.ts +410 -0
- package/src/cli/program.ts +992 -0
- package/src/cli/render/format.ts +69 -0
- package/src/cli/render/reporter.ts +643 -0
- package/src/cli/render/tty.ts +314 -0
- package/src/cli/run-options.ts +82 -0
- package/src/cli/skills.ts +129 -0
- package/src/core/annotation.ts +104 -0
- package/src/core/answerer.ts +75 -0
- package/src/core/artifact-evidence.ts +249 -0
- package/src/core/artifact-snapshot.ts +429 -0
- package/src/core/assertion.ts +156 -0
- package/src/core/case.ts +103 -0
- package/src/core/conversation-turn.ts +47 -0
- package/src/core/driver.ts +75 -0
- package/src/core/emitter.ts +38 -0
- package/src/core/events.ts +135 -0
- package/src/core/exec.ts +263 -0
- package/src/core/experiment.ts +467 -0
- package/src/core/glob.ts +31 -0
- package/src/core/hash.ts +237 -0
- package/src/core/index.ts +28 -0
- package/src/core/lifecycle-fixtures.ts +145 -0
- package/src/core/lifecycle-hooks.ts +101 -0
- package/src/core/mcp-events.ts +57 -0
- package/src/core/model-cli.ts +271 -0
- package/src/core/observer.ts +50 -0
- package/src/core/platform-process.ts +284 -0
- package/src/core/registry.ts +57 -0
- package/src/core/rubric.ts +52 -0
- package/src/core/run.ts +144 -0
- package/src/core/statistics.ts +194 -0
- package/src/core/subject.ts +50 -0
- package/src/core/tool-events.ts +292 -0
- package/src/core/usage.ts +123 -0
- package/src/core/wire.ts +135 -0
- package/src/core/zod.ts +20 -0
- package/src/drivers/claude-code/index.ts +866 -0
- package/src/drivers/claude-code/parse-events.ts +464 -0
- package/src/drivers/codex/app-events.ts +218 -0
- package/src/drivers/codex/config.ts +128 -0
- package/src/drivers/codex/index.ts +995 -0
- package/src/drivers/mcp/index.ts +351 -0
- package/src/drivers/mcp/transport.ts +222 -0
- package/src/drivers/shared/artifacts.ts +86 -0
- package/src/drivers/shared/secure-copy.ts +40 -0
- package/src/drivers/subprocess/index.ts +490 -0
- package/src/index.ts +594 -0
- package/src/optimization/reflective.ts +319 -0
- package/src/orchestrator/annotation-loader.ts +97 -0
- package/src/orchestrator/annotation-store.ts +112 -0
- package/src/orchestrator/authored-dependencies.ts +193 -0
- package/src/orchestrator/campaign-lifecycle.ts +181 -0
- package/src/orchestrator/candidate-integrity.ts +18 -0
- package/src/orchestrator/candidates.ts +683 -0
- package/src/orchestrator/compare.ts +358 -0
- package/src/orchestrator/comparison-identity.ts +45 -0
- package/src/orchestrator/config.ts +205 -0
- package/src/orchestrator/content-bound-json.ts +71 -0
- package/src/orchestrator/discover.ts +132 -0
- package/src/orchestrator/doctor.ts +781 -0
- package/src/orchestrator/driver-session.ts +507 -0
- package/src/orchestrator/evaluator-identity.ts +45 -0
- package/src/orchestrator/experiment-budget.ts +61 -0
- package/src/orchestrator/experiment-context.ts +41 -0
- package/src/orchestrator/experiment-contracts.ts +43 -0
- package/src/orchestrator/experiment-corpus.ts +416 -0
- package/src/orchestrator/experiment-evaluation.ts +749 -0
- package/src/orchestrator/experiment-finish.ts +33 -0
- package/src/orchestrator/experiment-identity.ts +356 -0
- package/src/orchestrator/experiment-loader.ts +105 -0
- package/src/orchestrator/experiment-report.ts +788 -0
- package/src/orchestrator/experiment-runs.ts +960 -0
- package/src/orchestrator/experiment-runtime-identity.ts +465 -0
- package/src/orchestrator/experiment-selection.ts +467 -0
- package/src/orchestrator/experiment.ts +991 -0
- package/src/orchestrator/fixture-identity.ts +67 -0
- package/src/orchestrator/fs-read.ts +126 -0
- package/src/orchestrator/grader-experiment.ts +856 -0
- package/src/orchestrator/grading.ts +260 -0
- package/src/orchestrator/index.ts +186 -0
- package/src/orchestrator/lifecycle-hook-execution.ts +90 -0
- package/src/orchestrator/list-runs.ts +384 -0
- package/src/orchestrator/loader.ts +19 -0
- package/src/orchestrator/optimization-lifecycle.ts +126 -0
- package/src/orchestrator/orchestrator.ts +919 -0
- package/src/orchestrator/owned-lock.ts +147 -0
- package/src/orchestrator/package-version.ts +5 -0
- package/src/orchestrator/provenance.ts +140 -0
- package/src/orchestrator/regrade.ts +580 -0
- package/src/orchestrator/rubric-loader.ts +74 -0
- package/src/orchestrator/run-dir.ts +209 -0
- package/src/orchestrator/run-execution.ts +694 -0
- package/src/orchestrator/run-lifecycle.ts +174 -0
- package/src/orchestrator/suite-compare.ts +195 -0
- package/src/orchestrator/suite-lifecycle.ts +335 -0
- package/src/orchestrator/suite-runs.ts +202 -0
- package/src/orchestrator/suite-summary.ts +276 -0
- package/src/orchestrator/trace-limits.ts +65 -0
- package/src/orchestrator/validate-plugin-config.ts +34 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { getDefaultEnvironment } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
type AgentDriver,
|
|
6
|
+
type Answer,
|
|
7
|
+
type Artifact,
|
|
8
|
+
type DriverEventMap,
|
|
9
|
+
type DriverPlugin,
|
|
10
|
+
type ListedTool,
|
|
11
|
+
MCP_INIT_KIND,
|
|
12
|
+
MCP_TOOLS_LIST_KIND,
|
|
13
|
+
type McpInitPayload,
|
|
14
|
+
type Progress,
|
|
15
|
+
type ReadyState,
|
|
16
|
+
type StartContext,
|
|
17
|
+
TOOL_CALL_KIND,
|
|
18
|
+
TOOL_RESULT_KIND,
|
|
19
|
+
type ToolResultPayload,
|
|
20
|
+
TypedEmitter,
|
|
21
|
+
} from "../../core/index.js";
|
|
22
|
+
import { McpStdioClientTransport } from "./transport.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A single probe step. The driver speaks MCP directly — no LLM — so a "prompt"
|
|
26
|
+
* has no meaning here; the *probe* is the script. `list-tools` captures the
|
|
27
|
+
* `tools/list` surface; `call` invokes one tool and captures its result.
|
|
28
|
+
*/
|
|
29
|
+
const ProbeStepSchema = z.discriminatedUnion("op", [
|
|
30
|
+
z.object({ op: z.literal("list-tools") }).strict(),
|
|
31
|
+
z
|
|
32
|
+
.object({
|
|
33
|
+
op: z.literal("call"),
|
|
34
|
+
tool: z.string(),
|
|
35
|
+
args: z.record(z.string(), z.unknown()).default({}),
|
|
36
|
+
})
|
|
37
|
+
.strict(),
|
|
38
|
+
]);
|
|
39
|
+
export type ProbeStep = z.infer<typeof ProbeStepSchema>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Deterministic, no-LLM driver that spawns a local MCP server over stdio,
|
|
43
|
+
* runs the `initialize`/`tools/list`/`tools/call` handshake, and maps the
|
|
44
|
+
* protocol onto Lux's event model: `tool-call` (shared kind, so `tool-called*`
|
|
45
|
+
* assertions work), `tool-result` (the result the server returned), plus
|
|
46
|
+
* `mcp-init` and `mcp-tools-list` protocol captures. A tool that returns
|
|
47
|
+
* `isError: true` is a normal, gradable result; only a protocol/transport
|
|
48
|
+
* failure ends the run as a driver `error`. This is the flake-free tier where
|
|
49
|
+
* `--loop` pass^k is meaningful, and the code-free way to wrap any local
|
|
50
|
+
* stdio MCP server in evals.
|
|
51
|
+
*/
|
|
52
|
+
export const McpProbeDriverConfigSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
/** Executable to spawn the server (e.g. "node", "npx"). */
|
|
55
|
+
command: z.string(),
|
|
56
|
+
/**
|
|
57
|
+
* Args to the server. `${ARTIFACTS_DIR}` and `${RUN_DIR}` are expanded per
|
|
58
|
+
* run — e.g. pass `${ARTIFACTS_DIR}` as a server's working-directory arg.
|
|
59
|
+
*/
|
|
60
|
+
args: z.array(z.string()).default([]),
|
|
61
|
+
/** Working directory of the server process. Defaults to the run's artifacts dir. */
|
|
62
|
+
cwd: z.string().optional(),
|
|
63
|
+
/** Extra env for the server. Values also expand `${ARTIFACTS_DIR}`/`${RUN_DIR}`. */
|
|
64
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
65
|
+
/**
|
|
66
|
+
* Names of variables to forward from the harness env into the server. The
|
|
67
|
+
* SDK stdio transport otherwise inherits only a safe allowlist, so secrets
|
|
68
|
+
* like PRISM_REFRESH_TOKEN must be named here — by NAME, never frozen into
|
|
69
|
+
* the case.
|
|
70
|
+
*/
|
|
71
|
+
inheritEnv: z.array(z.string()).optional(),
|
|
72
|
+
/** The steps to run. Defaults to a single `tools/list` capture. */
|
|
73
|
+
probe: z.array(ProbeStepSchema).default([{ op: "list-tools" }]),
|
|
74
|
+
/** Timeout for the initialize handshake. */
|
|
75
|
+
startupTimeoutMs: z.number().positive().default(30_000),
|
|
76
|
+
/** Timeout for each `tools/list` / `tools/call` request. */
|
|
77
|
+
stepTimeoutMs: z.number().positive().default(60_000),
|
|
78
|
+
/** Safety cap for a paginated `tools/list` operation. */
|
|
79
|
+
maxListPages: z.number().int().positive().default(100),
|
|
80
|
+
})
|
|
81
|
+
.strict();
|
|
82
|
+
export type McpProbeDriverConfig = z.infer<typeof McpProbeDriverConfigSchema>;
|
|
83
|
+
|
|
84
|
+
const ARTIFACTS_DIR_PLACEHOLDER = `\${ARTIFACTS_DIR}`;
|
|
85
|
+
const RUN_DIR_PLACEHOLDER = `\${RUN_DIR}`;
|
|
86
|
+
let stderrWritable = true;
|
|
87
|
+
|
|
88
|
+
const writeServerStderr = (line: string): void => {
|
|
89
|
+
if (!stderrWritable) return;
|
|
90
|
+
stderrWritable = process.stderr.write(`[mcp-server] ${line}\n`);
|
|
91
|
+
if (!stderrWritable) {
|
|
92
|
+
process.stderr.once("drain", () => {
|
|
93
|
+
stderrWritable = true;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Expand the per-run path placeholders a case may embed in args/env/cwd. */
|
|
99
|
+
export const expandPlaceholders = (
|
|
100
|
+
value: string,
|
|
101
|
+
ctx: { runDir: string; artifactsDir: string },
|
|
102
|
+
): string =>
|
|
103
|
+
value
|
|
104
|
+
.replaceAll(ARTIFACTS_DIR_PLACEHOLDER, ctx.artifactsDir)
|
|
105
|
+
.replaceAll(RUN_DIR_PLACEHOLDER, ctx.runDir);
|
|
106
|
+
|
|
107
|
+
/** Concatenate the text blocks of an MCP result's content, for substring checks. */
|
|
108
|
+
const flattenTextContent = (content: unknown): string => {
|
|
109
|
+
if (!Array.isArray(content)) return "";
|
|
110
|
+
return content
|
|
111
|
+
.filter(
|
|
112
|
+
(b): b is { type: "text"; text: string } =>
|
|
113
|
+
typeof b === "object" &&
|
|
114
|
+
b !== null &&
|
|
115
|
+
(b as { type?: unknown }).type === "text" &&
|
|
116
|
+
typeof (b as { text?: unknown }).text === "string",
|
|
117
|
+
)
|
|
118
|
+
.map((b) => b.text)
|
|
119
|
+
.join("\n");
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Project an SDK `CallToolResult` (either the modern or legacy shape) onto Lux's `ToolResultPayload`. */
|
|
123
|
+
const toToolResultPayload = (
|
|
124
|
+
name: string,
|
|
125
|
+
toolUseId: string,
|
|
126
|
+
result: unknown,
|
|
127
|
+
durationMs: number,
|
|
128
|
+
): ToolResultPayload => {
|
|
129
|
+
const r = (typeof result === "object" && result !== null ? result : {}) as {
|
|
130
|
+
content?: unknown;
|
|
131
|
+
structuredContent?: unknown;
|
|
132
|
+
isError?: unknown;
|
|
133
|
+
};
|
|
134
|
+
const structured = r.structuredContent;
|
|
135
|
+
return {
|
|
136
|
+
name,
|
|
137
|
+
toolUseId,
|
|
138
|
+
content: r.content ?? [],
|
|
139
|
+
...(structured !== null && typeof structured === "object"
|
|
140
|
+
? { structuredContent: structured as Record<string, unknown> }
|
|
141
|
+
: {}),
|
|
142
|
+
isError: r.isError === true,
|
|
143
|
+
text: flattenTextContent(r.content),
|
|
144
|
+
durationMs,
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const buildServerEnv = (
|
|
149
|
+
config: McpProbeDriverConfig,
|
|
150
|
+
expand: (s: string) => string,
|
|
151
|
+
): Record<string, string> => {
|
|
152
|
+
const env: Record<string, string> = { ...getDefaultEnvironment() };
|
|
153
|
+
for (const name of config.inheritEnv ?? []) {
|
|
154
|
+
const value = process.env[name];
|
|
155
|
+
if (value !== undefined) env[name] = value;
|
|
156
|
+
}
|
|
157
|
+
for (const [key, value] of Object.entries(config.env ?? {})) env[key] = expand(value);
|
|
158
|
+
return env;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
class McpProbeDriverImpl implements AgentDriver {
|
|
162
|
+
private readonly emitter = new TypedEmitter<DriverEventMap>();
|
|
163
|
+
private readonly config: McpProbeDriverConfig;
|
|
164
|
+
private client: Client | null = null;
|
|
165
|
+
private transport: McpStdioClientTransport | null = null;
|
|
166
|
+
private seq = 0;
|
|
167
|
+
private closePromise: Promise<void> | null = null;
|
|
168
|
+
private terminalSent = false;
|
|
169
|
+
private onAbort: (() => void) | null = null;
|
|
170
|
+
private abortSignal: AbortSignal | null = null;
|
|
171
|
+
|
|
172
|
+
constructor(config: McpProbeDriverConfig) {
|
|
173
|
+
this.config = config;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
on<K extends keyof DriverEventMap>(event: K, handler: (e: DriverEventMap[K]) => void): void {
|
|
177
|
+
this.emitter.on(event, handler);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
off<K extends keyof DriverEventMap>(event: K, handler: (e: DriverEventMap[K]) => void): void {
|
|
181
|
+
this.emitter.off(event, handler);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private progress(kind: string, payload: unknown): Progress {
|
|
185
|
+
return { id: `mcp-${this.seq++}`, kind, payload, ts: Date.now() };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async start(ctx: StartContext): Promise<ReadyState> {
|
|
189
|
+
const expand = (s: string): string => expandPlaceholders(s, ctx);
|
|
190
|
+
const args = this.config.args.map(expand);
|
|
191
|
+
const cwd = this.config.cwd ? expand(this.config.cwd) : ctx.artifactsDir;
|
|
192
|
+
const env = buildServerEnv(this.config, expand);
|
|
193
|
+
|
|
194
|
+
this.abortSignal = ctx.abortSignal;
|
|
195
|
+
this.onAbort = () => void this.close();
|
|
196
|
+
if (ctx.abortSignal.aborted) {
|
|
197
|
+
throw new Error("mcp-probe driver: aborted before start");
|
|
198
|
+
}
|
|
199
|
+
ctx.abortSignal.addEventListener("abort", this.onAbort, { once: true });
|
|
200
|
+
|
|
201
|
+
const transport = new McpStdioClientTransport({
|
|
202
|
+
command: this.config.command,
|
|
203
|
+
args,
|
|
204
|
+
env,
|
|
205
|
+
cwd,
|
|
206
|
+
writeStderr: writeServerStderr,
|
|
207
|
+
});
|
|
208
|
+
const client = new Client({ name: "lux-mcp-probe", version: "1.0.0" });
|
|
209
|
+
this.transport = transport;
|
|
210
|
+
this.client = client;
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
await client.connect(transport, {
|
|
214
|
+
timeout: this.config.startupTimeoutMs,
|
|
215
|
+
maxTotalTimeout: this.config.startupTimeoutMs,
|
|
216
|
+
signal: ctx.abortSignal,
|
|
217
|
+
});
|
|
218
|
+
} catch (err) {
|
|
219
|
+
await this.close();
|
|
220
|
+
const failure = transport.failure ?? err;
|
|
221
|
+
throw new Error(
|
|
222
|
+
`mcp-probe driver: ${failure instanceof Error ? failure.message : String(failure)}`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const serverInfo = client.getServerVersion();
|
|
227
|
+
const capabilities = client.getServerCapabilities();
|
|
228
|
+
const instructions = client.getInstructions();
|
|
229
|
+
const init: McpInitPayload = {
|
|
230
|
+
...(serverInfo ? { serverInfo: { name: serverInfo.name, version: serverInfo.version } } : {}),
|
|
231
|
+
...(capabilities ? { capabilities: capabilities as Record<string, unknown> } : {}),
|
|
232
|
+
...(instructions ? { instructions } : {}),
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Probe after start() resolves so the orchestrator records `ready` first.
|
|
236
|
+
setImmediate(() => void this.runProbe(init));
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
agentId: "mcp-probe",
|
|
240
|
+
detail: {
|
|
241
|
+
command: this.config.command,
|
|
242
|
+
...(serverInfo?.version ? { serverVersion: serverInfo.version } : {}),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private async runProbe(init: McpInitPayload): Promise<void> {
|
|
248
|
+
const client = this.client;
|
|
249
|
+
if (!client) return;
|
|
250
|
+
try {
|
|
251
|
+
await this.emitter.emit("progress", this.progress(MCP_INIT_KIND, init));
|
|
252
|
+
|
|
253
|
+
for (const step of this.config.probe) {
|
|
254
|
+
if (this.terminalSent) return;
|
|
255
|
+
if (step.op === "list-tools") {
|
|
256
|
+
const tools: ListedTool[] = [];
|
|
257
|
+
const seenCursors = new Set<string>();
|
|
258
|
+
let cursor: string | undefined;
|
|
259
|
+
for (let page = 1; ; page++) {
|
|
260
|
+
if (page > this.config.maxListPages) {
|
|
261
|
+
throw new Error(`tools/list exceeded ${this.config.maxListPages} pages`);
|
|
262
|
+
}
|
|
263
|
+
const result = await client.listTools(cursor ? { cursor } : undefined, {
|
|
264
|
+
timeout: this.config.stepTimeoutMs,
|
|
265
|
+
maxTotalTimeout: this.config.stepTimeoutMs,
|
|
266
|
+
...(this.abortSignal ? { signal: this.abortSignal } : {}),
|
|
267
|
+
});
|
|
268
|
+
tools.push(...(result.tools as ListedTool[]));
|
|
269
|
+
cursor = result.nextCursor;
|
|
270
|
+
if (!cursor) break;
|
|
271
|
+
if (seenCursors.has(cursor)) {
|
|
272
|
+
throw new Error(`tools/list repeated cursor '${cursor}'`);
|
|
273
|
+
}
|
|
274
|
+
seenCursors.add(cursor);
|
|
275
|
+
}
|
|
276
|
+
await this.emitter.emit("progress", this.progress(MCP_TOOLS_LIST_KIND, { tools }));
|
|
277
|
+
} else {
|
|
278
|
+
const toolUseId = `probe-${this.seq}`;
|
|
279
|
+
await this.emitter.emit(
|
|
280
|
+
"progress",
|
|
281
|
+
this.progress(TOOL_CALL_KIND, { name: step.tool, input: step.args, toolUseId }),
|
|
282
|
+
);
|
|
283
|
+
const startedAt = Date.now();
|
|
284
|
+
const result = await client.callTool(
|
|
285
|
+
{ name: step.tool, arguments: step.args },
|
|
286
|
+
undefined,
|
|
287
|
+
{
|
|
288
|
+
timeout: this.config.stepTimeoutMs,
|
|
289
|
+
maxTotalTimeout: this.config.stepTimeoutMs,
|
|
290
|
+
...(this.abortSignal ? { signal: this.abortSignal } : {}),
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
await this.emitter.emit(
|
|
294
|
+
"progress",
|
|
295
|
+
this.progress(
|
|
296
|
+
TOOL_RESULT_KIND,
|
|
297
|
+
toToolResultPayload(step.tool, toolUseId, result, Date.now() - startedAt),
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (this.terminalSent) return;
|
|
304
|
+
this.terminalSent = true;
|
|
305
|
+
await this.emitter.emit("done", {
|
|
306
|
+
exitReason: "done",
|
|
307
|
+
summary: { serverInfo: init.serverInfo },
|
|
308
|
+
});
|
|
309
|
+
} catch (err) {
|
|
310
|
+
if (this.terminalSent) return;
|
|
311
|
+
this.terminalSent = true;
|
|
312
|
+
const failure = this.transport?.failure ?? err;
|
|
313
|
+
await this.emitter.emit("error", {
|
|
314
|
+
exitReason: "error",
|
|
315
|
+
reason: failure instanceof Error ? failure.message : String(failure),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async respond(_answer: Answer): Promise<void> {
|
|
321
|
+
throw new Error("mcp-probe driver does not support interrupts");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async collect(): Promise<Artifact[]> {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
quiesce(): Promise<void> {
|
|
329
|
+
return this.close();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
close(): Promise<void> {
|
|
333
|
+
this.closePromise ??= this.closeResources();
|
|
334
|
+
return this.closePromise;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private async closeResources(): Promise<void> {
|
|
338
|
+
if (this.onAbort && this.abortSignal) {
|
|
339
|
+
this.abortSignal.removeEventListener("abort", this.onAbort);
|
|
340
|
+
}
|
|
341
|
+
await this.client?.close().catch(() => {});
|
|
342
|
+
await this.transport?.close().catch(() => {});
|
|
343
|
+
this.emitter.removeAllListeners();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export const mcpProbeDriver: DriverPlugin<McpProbeDriverConfig> = {
|
|
348
|
+
name: "mcp-probe",
|
|
349
|
+
configSchema: McpProbeDriverConfigSchema,
|
|
350
|
+
create: (config) => new McpProbeDriverImpl(config),
|
|
351
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
+
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { type ManagedProcess, spawnManagedProcess } from "../../core/index.js";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MAX_PROTOCOL_LINE_BYTES = 10_000_000;
|
|
7
|
+
const DEFAULT_MAX_STDERR_TAIL_CHARS = 8_192;
|
|
8
|
+
const DEFAULT_KILL_GRACE_MS = 5_000;
|
|
9
|
+
|
|
10
|
+
export type McpStdioTransportOptions = {
|
|
11
|
+
command: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
cwd?: string;
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
killGraceMs?: number;
|
|
16
|
+
maxProtocolLineBytes?: number;
|
|
17
|
+
maxStderrTailChars?: number;
|
|
18
|
+
writeStderr?: (line: string) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* MCP's stdio framing with Lux-owned process-group and memory boundaries.
|
|
23
|
+
* Protocol messages remain SDK-validated, but no SDK process lifecycle leaks
|
|
24
|
+
* through this transport.
|
|
25
|
+
*/
|
|
26
|
+
export class McpStdioClientTransport implements Transport {
|
|
27
|
+
onclose?: () => void;
|
|
28
|
+
onerror?: (error: Error) => void;
|
|
29
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
30
|
+
|
|
31
|
+
private readonly options: McpStdioTransportOptions;
|
|
32
|
+
private readonly maxProtocolLineBytes: number;
|
|
33
|
+
private readonly maxStderrTailChars: number;
|
|
34
|
+
private process: ManagedProcess | null = null;
|
|
35
|
+
private stdoutChunks: Buffer[] = [];
|
|
36
|
+
private stdoutBytes = 0;
|
|
37
|
+
private stderrTail = "";
|
|
38
|
+
private stderrTruncated = false;
|
|
39
|
+
private closePromise: Promise<void> | null = null;
|
|
40
|
+
private closeNotified = false;
|
|
41
|
+
private started = false;
|
|
42
|
+
private closing = false;
|
|
43
|
+
private transportError: Error | null = null;
|
|
44
|
+
|
|
45
|
+
constructor(options: McpStdioTransportOptions) {
|
|
46
|
+
this.options = options;
|
|
47
|
+
this.maxProtocolLineBytes = options.maxProtocolLineBytes ?? DEFAULT_MAX_PROTOCOL_LINE_BYTES;
|
|
48
|
+
this.maxStderrTailChars = options.maxStderrTailChars ?? DEFAULT_MAX_STDERR_TAIL_CHARS;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get failure(): Error | null {
|
|
52
|
+
return this.transportError;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get pid(): number | null {
|
|
56
|
+
return this.process?.child.pid ?? null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async start(): Promise<void> {
|
|
60
|
+
if (this.started) throw new Error("MCP stdio transport already started");
|
|
61
|
+
this.started = true;
|
|
62
|
+
|
|
63
|
+
const managed = spawnManagedProcess(this.options.command, this.options.args ?? [], {
|
|
64
|
+
killGraceMs: this.options.killGraceMs ?? DEFAULT_KILL_GRACE_MS,
|
|
65
|
+
...(this.options.cwd === undefined ? {} : { cwd: this.options.cwd }),
|
|
66
|
+
...(this.options.env === undefined ? {} : { env: this.options.env }),
|
|
67
|
+
});
|
|
68
|
+
this.process = managed;
|
|
69
|
+
const { child } = managed;
|
|
70
|
+
|
|
71
|
+
child.stdout.on("data", (chunk: Buffer) => this.readStdout(chunk));
|
|
72
|
+
child.stdout.on("end", () => {
|
|
73
|
+
if (!this.closing && this.stdoutBytes > 0) {
|
|
74
|
+
this.fail(
|
|
75
|
+
new Error(
|
|
76
|
+
`MCP server stdout ended with an unterminated ${this.stdoutBytes}-byte protocol line`,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
this.clearStdout();
|
|
81
|
+
});
|
|
82
|
+
child.stdout.on("error", (err) => this.fail(new Error(`MCP server stdout: ${err.message}`)));
|
|
83
|
+
|
|
84
|
+
child.stderr.setEncoding("utf8");
|
|
85
|
+
child.stderr.on("data", (chunk: string) => this.readStderr(chunk));
|
|
86
|
+
child.stderr.on("end", () => this.flushStderr());
|
|
87
|
+
child.stderr.on("error", (err) => this.fail(new Error(`MCP server stderr: ${err.message}`)));
|
|
88
|
+
|
|
89
|
+
void managed.exited.then(() => {
|
|
90
|
+
this.flushStderr();
|
|
91
|
+
this.clearStdout();
|
|
92
|
+
this.process = null;
|
|
93
|
+
this.notifyClose();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await managed.spawned;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
100
|
+
this.transportError ??= error;
|
|
101
|
+
await managed.terminate();
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async send(message: JSONRPCMessage): Promise<void> {
|
|
107
|
+
const stdin = this.process?.child.stdin;
|
|
108
|
+
if (!stdin || this.closing || stdin.destroyed) throw new Error("MCP stdio transport is closed");
|
|
109
|
+
const serialized = `${JSON.stringify(message)}\n`;
|
|
110
|
+
await new Promise<void>((resolve, reject) => {
|
|
111
|
+
stdin.write(serialized, (err) => {
|
|
112
|
+
if (err) reject(new Error(`MCP server stdin: ${err.message}`, { cause: err }));
|
|
113
|
+
else resolve();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
close(): Promise<void> {
|
|
119
|
+
if (this.closePromise) return this.closePromise;
|
|
120
|
+
this.closing = true;
|
|
121
|
+
this.closePromise = (async () => {
|
|
122
|
+
const managed = this.process;
|
|
123
|
+
if (managed) {
|
|
124
|
+
managed.child.stdin.end();
|
|
125
|
+
await managed.terminate();
|
|
126
|
+
}
|
|
127
|
+
this.clearStdout();
|
|
128
|
+
this.flushStderr();
|
|
129
|
+
this.notifyClose();
|
|
130
|
+
})();
|
|
131
|
+
return this.closePromise;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private readStdout(chunk: Buffer): void {
|
|
135
|
+
if (this.transportError || this.closing) return;
|
|
136
|
+
let offset = 0;
|
|
137
|
+
while (offset < chunk.length) {
|
|
138
|
+
const newline = chunk.indexOf(0x0a, offset);
|
|
139
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
140
|
+
const segment = chunk.subarray(offset, end);
|
|
141
|
+
const lineBytes = this.stdoutBytes + segment.length;
|
|
142
|
+
if (lineBytes > this.maxProtocolLineBytes) {
|
|
143
|
+
this.fail(
|
|
144
|
+
new Error(`MCP server stdout protocol line exceeded ${this.maxProtocolLineBytes} bytes`),
|
|
145
|
+
);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (segment.length > 0) {
|
|
149
|
+
this.stdoutChunks.push(segment);
|
|
150
|
+
this.stdoutBytes = lineBytes;
|
|
151
|
+
}
|
|
152
|
+
if (newline === -1) return;
|
|
153
|
+
this.dispatchLine(Buffer.concat(this.stdoutChunks, this.stdoutBytes));
|
|
154
|
+
if (this.transportError) return;
|
|
155
|
+
this.clearStdout();
|
|
156
|
+
offset = newline + 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private clearStdout(): void {
|
|
161
|
+
this.stdoutChunks = [];
|
|
162
|
+
this.stdoutBytes = 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private dispatchLine(lineBuffer: Buffer): void {
|
|
166
|
+
const line = lineBuffer.toString("utf8").replace(/\r$/, "");
|
|
167
|
+
try {
|
|
168
|
+
const message = JSONRPCMessageSchema.parse(JSON.parse(line));
|
|
169
|
+
this.onmessage?.(message);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
this.fail(
|
|
172
|
+
new Error(
|
|
173
|
+
`MCP server emitted an invalid JSON-RPC line: ${
|
|
174
|
+
err instanceof Error ? err.message : String(err)
|
|
175
|
+
}`,
|
|
176
|
+
{ cause: err },
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private readStderr(chunk: string): void {
|
|
183
|
+
let offset = 0;
|
|
184
|
+
while (offset < chunk.length) {
|
|
185
|
+
const newline = chunk.indexOf("\n", offset);
|
|
186
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
187
|
+
this.appendStderr(chunk.slice(offset, end));
|
|
188
|
+
if (newline === -1) return;
|
|
189
|
+
this.flushStderr();
|
|
190
|
+
offset = newline + 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private appendStderr(text: string): void {
|
|
195
|
+
if (text.length === 0) return;
|
|
196
|
+
if (this.stderrTail.length + text.length > this.maxStderrTailChars) {
|
|
197
|
+
this.stderrTruncated = true;
|
|
198
|
+
}
|
|
199
|
+
this.stderrTail = `${this.stderrTail}${text}`.slice(-this.maxStderrTailChars);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private flushStderr(): void {
|
|
203
|
+
if (!this.stderrTail && !this.stderrTruncated) return;
|
|
204
|
+
const line = this.stderrTail.replace(/\r$/, "");
|
|
205
|
+
this.options.writeStderr?.(this.stderrTruncated ? `[truncated] …${line}` : line);
|
|
206
|
+
this.stderrTail = "";
|
|
207
|
+
this.stderrTruncated = false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private fail(error: Error): void {
|
|
211
|
+
if (this.transportError || this.closing) return;
|
|
212
|
+
this.transportError = error;
|
|
213
|
+
this.onerror?.(error);
|
|
214
|
+
void this.close();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private notifyClose(): void {
|
|
218
|
+
if (this.closeNotified) return;
|
|
219
|
+
this.closeNotified = true;
|
|
220
|
+
this.onclose?.();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { lstat, readdir, realpath } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
ARTIFACT_SNAPSHOT_MANIFEST,
|
|
5
|
+
type Artifact,
|
|
6
|
+
readArtifactSnapshotManifest,
|
|
7
|
+
} from "../../core/index.js";
|
|
8
|
+
|
|
9
|
+
const IGNORED_DIRS = new Set(["node_modules", "lib", "dist"]);
|
|
10
|
+
|
|
11
|
+
/** Enumerate agent-authored files without following symlinks or build/dependency trees. */
|
|
12
|
+
export const walkArtifacts = async (root: string, max = 500): Promise<Artifact[]> => {
|
|
13
|
+
if (!Number.isSafeInteger(max) || max <= 0) {
|
|
14
|
+
throw new RangeError("artifact collection max must be a positive safe integer");
|
|
15
|
+
}
|
|
16
|
+
const requestedRoot = resolve(root);
|
|
17
|
+
const rootMetadata = await lstat(requestedRoot);
|
|
18
|
+
if (!rootMetadata.isDirectory() || rootMetadata.isSymbolicLink()) {
|
|
19
|
+
throw new Error(`artifact root must be a real directory: ${root}`);
|
|
20
|
+
}
|
|
21
|
+
const canonicalRoot = await realpath(requestedRoot);
|
|
22
|
+
const out: Artifact[] = [];
|
|
23
|
+
const addArtifact = (path: string, size: number): void => {
|
|
24
|
+
if (out.length >= max) {
|
|
25
|
+
throw new Error(`artifact collection exceeds ${max} files: ${root}`);
|
|
26
|
+
}
|
|
27
|
+
out.push({ path, size });
|
|
28
|
+
};
|
|
29
|
+
const collectSnapshot = async (dir: string): Promise<boolean> => {
|
|
30
|
+
const manifest = await readArtifactSnapshotManifest(dir);
|
|
31
|
+
if (!manifest) return false;
|
|
32
|
+
if (manifest.name !== basename(dir)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`artifact snapshot manifest names ${manifest.name}, expected ${basename(dir)}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const canonicalSnapshotRoot = await realpath(dir);
|
|
38
|
+
for (const file of manifest.files) {
|
|
39
|
+
const full = resolve(dir, file.path);
|
|
40
|
+
const canonicalParent = await realpath(dirname(full));
|
|
41
|
+
if (
|
|
42
|
+
canonicalParent !== canonicalSnapshotRoot &&
|
|
43
|
+
!canonicalParent.startsWith(`${canonicalSnapshotRoot}${sep}`)
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(`artifact snapshot path resolves outside its root: ${file.path}`);
|
|
46
|
+
}
|
|
47
|
+
const metadata = await lstat(full);
|
|
48
|
+
if (metadata.isSymbolicLink() || !metadata.isFile() || metadata.size !== file.bytes) {
|
|
49
|
+
throw new Error(`artifact snapshot file does not match its manifest: ${file.path}`);
|
|
50
|
+
}
|
|
51
|
+
addArtifact(relative(requestedRoot, full).split(sep).join("/"), metadata.size);
|
|
52
|
+
}
|
|
53
|
+
const manifestPath = join(dir, ARTIFACT_SNAPSHOT_MANIFEST);
|
|
54
|
+
const manifestStats = await lstat(manifestPath);
|
|
55
|
+
addArtifact(relative(requestedRoot, manifestPath).split(sep).join("/"), manifestStats.size);
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
58
|
+
const walk = async (dir: string): Promise<void> => {
|
|
59
|
+
const canonicalDirectory = await realpath(dir);
|
|
60
|
+
if (
|
|
61
|
+
canonicalDirectory !== canonicalRoot &&
|
|
62
|
+
!canonicalDirectory.startsWith(`${canonicalRoot}${sep}`)
|
|
63
|
+
) {
|
|
64
|
+
throw new Error(`artifact directory resolves outside its root: ${dir}`);
|
|
65
|
+
}
|
|
66
|
+
if (await collectSnapshot(dir)) return;
|
|
67
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
68
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (entry.isSymbolicLink()) continue;
|
|
71
|
+
const full = join(dir, entry.name);
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
if (await collectSnapshot(full)) continue;
|
|
74
|
+
if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
|
|
75
|
+
await walk(full);
|
|
76
|
+
} else if (entry.isFile()) {
|
|
77
|
+
const metadata = await lstat(full);
|
|
78
|
+
if (metadata.isFile()) {
|
|
79
|
+
addArtifact(relative(requestedRoot, full).split(sep).join("/"), metadata.size);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
await walk(requestedRoot);
|
|
85
|
+
return out;
|
|
86
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { constants, lstat, open, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { resolve, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
const isWithin = (parent: string, child: string): boolean => {
|
|
5
|
+
const root = resolve(parent);
|
|
6
|
+
const path = resolve(child);
|
|
7
|
+
return path === root || path.startsWith(`${root}${sep}`);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const copyRegularFileNoFollow = async (
|
|
11
|
+
sourceRoot: string,
|
|
12
|
+
sourcePath: string,
|
|
13
|
+
targetPath: string,
|
|
14
|
+
): Promise<void> => {
|
|
15
|
+
if ((await lstat(sourcePath)).isSymbolicLink()) {
|
|
16
|
+
throw new Error(`artifact cannot be a symlink: ${sourcePath}`);
|
|
17
|
+
}
|
|
18
|
+
const source = await open(sourcePath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
19
|
+
let target: Awaited<ReturnType<typeof open>> | undefined;
|
|
20
|
+
try {
|
|
21
|
+
const sourceStat = await source.stat();
|
|
22
|
+
if (!sourceStat.isFile()) throw new Error(`artifact is not a regular file: ${sourcePath}`);
|
|
23
|
+
const resolvedSource = await realpath(sourcePath);
|
|
24
|
+
if (!isWithin(sourceRoot, resolvedSource)) {
|
|
25
|
+
throw new Error(`artifact resolves outside the isolated workspace: ${sourcePath}`);
|
|
26
|
+
}
|
|
27
|
+
const resolvedStat = await stat(resolvedSource);
|
|
28
|
+
if (
|
|
29
|
+
!resolvedStat.isFile() ||
|
|
30
|
+
resolvedStat.dev !== sourceStat.dev ||
|
|
31
|
+
resolvedStat.ino !== sourceStat.ino
|
|
32
|
+
) {
|
|
33
|
+
throw new Error(`artifact changed while being collected: ${sourcePath}`);
|
|
34
|
+
}
|
|
35
|
+
target = await open(targetPath, "wx", sourceStat.mode & 0o777);
|
|
36
|
+
await target.writeFile(source.createReadStream({ autoClose: false }));
|
|
37
|
+
} finally {
|
|
38
|
+
await Promise.allSettled([source.close(), ...(target ? [target.close()] : [])]);
|
|
39
|
+
}
|
|
40
|
+
};
|