@metaharness/darwin 0.1.0
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/README.md +221 -0
- package/SECURITY.md +200 -0
- package/dist/archive.d.ts +89 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +220 -0
- package/dist/archive.js.map +1 -0
- package/dist/bench/gates.d.ts +19 -0
- package/dist/bench/gates.d.ts.map +1 -0
- package/dist/bench/gates.js +82 -0
- package/dist/bench/gates.js.map +1 -0
- package/dist/bench/index.d.ts +11 -0
- package/dist/bench/index.d.ts.map +1 -0
- package/dist/bench/index.js +25 -0
- package/dist/bench/index.js.map +1 -0
- package/dist/bench/lineage.d.ts +60 -0
- package/dist/bench/lineage.d.ts.map +1 -0
- package/dist/bench/lineage.js +166 -0
- package/dist/bench/lineage.js.map +1 -0
- package/dist/bench/metrics.d.ts +32 -0
- package/dist/bench/metrics.d.ts.map +1 -0
- package/dist/bench/metrics.js +52 -0
- package/dist/bench/metrics.js.map +1 -0
- package/dist/bench/promotion.d.ts +21 -0
- package/dist/bench/promotion.d.ts.map +1 -0
- package/dist/bench/promotion.js +109 -0
- package/dist/bench/promotion.js.map +1 -0
- package/dist/bench/risk.d.ts +45 -0
- package/dist/bench/risk.d.ts.map +1 -0
- package/dist/bench/risk.js +71 -0
- package/dist/bench/risk.js.map +1 -0
- package/dist/bench/runner.d.ts +53 -0
- package/dist/bench/runner.d.ts.map +1 -0
- package/dist/bench/runner.js +131 -0
- package/dist/bench/runner.js.map +1 -0
- package/dist/bench/score.d.ts +16 -0
- package/dist/bench/score.d.ts.map +1 -0
- package/dist/bench/score.js +83 -0
- package/dist/bench/score.js.map +1 -0
- package/dist/bench/stats.d.ts +26 -0
- package/dist/bench/stats.d.ts.map +1 -0
- package/dist/bench/stats.js +74 -0
- package/dist/bench/stats.js.map +1 -0
- package/dist/bench/suite.d.ts +16 -0
- package/dist/bench/suite.d.ts.map +1 -0
- package/dist/bench/suite.js +59 -0
- package/dist/bench/suite.js.map +1 -0
- package/dist/bench/types.d.ts +135 -0
- package/dist/bench/types.d.ts.map +1 -0
- package/dist/bench/types.js +16 -0
- package/dist/bench/types.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -0
- package/dist/evolve.d.ts +11 -0
- package/dist/evolve.d.ts.map +1 -0
- package/dist/evolve.js +129 -0
- package/dist/evolve.js.map +1 -0
- package/dist/generator.d.ts +9 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +46 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/mutator.d.ts +61 -0
- package/dist/mutator.d.ts.map +1 -0
- package/dist/mutator.js +193 -0
- package/dist/mutator.js.map +1 -0
- package/dist/openrouter-mutator.d.ts +32 -0
- package/dist/openrouter-mutator.d.ts.map +1 -0
- package/dist/openrouter-mutator.js +81 -0
- package/dist/openrouter-mutator.js.map +1 -0
- package/dist/repo_profiler.d.ts +8 -0
- package/dist/repo_profiler.d.ts.map +1 -0
- package/dist/repo_profiler.js +127 -0
- package/dist/repo_profiler.js.map +1 -0
- package/dist/safety.d.ts +45 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +191 -0
- package/dist/safety.js.map +1 -0
- package/dist/sandbox.d.ts +24 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +153 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/scorer.d.ts +26 -0
- package/dist/scorer.d.ts.map +1 -0
- package/dist/scorer.js +168 -0
- package/dist/scorer.js.map +1 -0
- package/dist/templates.d.ts +37 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +309 -0
- package/dist/templates.js.map +1 -0
- package/dist/types.d.ts +123 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// OpenRouter-backed CodeGenerator (ADR-071 §contract) — the LLM mutator that
|
|
4
|
+
// "slots in behind the SAME validateGeneratedCode gate" as the DeterministicMutator.
|
|
5
|
+
// It asks a model to regenerate ONE surface file, improving it while preserving
|
|
6
|
+
// exported signatures and introducing NO new capabilities (so it survives the
|
|
7
|
+
// safety gate in createChildVariant). Real OpenRouter calls; no fabrication.
|
|
8
|
+
//
|
|
9
|
+
// Key: OPENROUTER_API_KEY env, or falls back to /tmp/.orkey. Model: env
|
|
10
|
+
// DARWIN_MUTATOR_MODEL (default anthropic/claude-haiku-4.5 — cheap tier).
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
function apiKey() {
|
|
13
|
+
const env = (process.env.OPENROUTER_API_KEY || '').trim();
|
|
14
|
+
if (env)
|
|
15
|
+
return env;
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync('/tmp/.orkey', 'utf8').trim();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new Error('OpenRouterMutator: no OPENROUTER_API_KEY (env or /tmp/.orkey)');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Strip a fenced code block if the model wrapped its output. */
|
|
24
|
+
function unfence(text) {
|
|
25
|
+
const m = text.match(/```(?:[a-zA-Z0-9]+)?\n([\s\S]*?)\n```/);
|
|
26
|
+
return (m ? m[1] : text).trim() + '\n';
|
|
27
|
+
}
|
|
28
|
+
export class OpenRouterMutator {
|
|
29
|
+
model;
|
|
30
|
+
maxTokens;
|
|
31
|
+
temperature;
|
|
32
|
+
telemetry = { calls: 0, promptTokens: 0, completionTokens: 0, costUSD: 0 };
|
|
33
|
+
constructor(opts = {}) {
|
|
34
|
+
this.model = opts.model ?? process.env.DARWIN_MUTATOR_MODEL ?? 'anthropic/claude-haiku-4.5';
|
|
35
|
+
this.maxTokens = opts.maxTokens ?? 2000;
|
|
36
|
+
this.temperature = opts.temperature ?? 0.4;
|
|
37
|
+
}
|
|
38
|
+
async generateMutation(input) {
|
|
39
|
+
const sys = 'You improve ONE file of an AI agent harness. Output ONLY the full replacement file — no prose, no fences. ' +
|
|
40
|
+
'HARD RULES: keep every exported name and signature identical; introduce NO new capabilities, imports, network, ' +
|
|
41
|
+
'filesystem, shell, or env access; no new dependencies; pure refactor/tuning only (it must pass a static safety ' +
|
|
42
|
+
'validator that rejects added capabilities). Make a small, plausibly score-improving change to the "' +
|
|
43
|
+
input.surface + '" surface.';
|
|
44
|
+
const user = `Surface: ${input.surface}\nParent score: ${input.parentScore}\n` +
|
|
45
|
+
(input.repoSummary ? `Repo: ${input.repoSummary}\n` : '') +
|
|
46
|
+
(input.failedTraces.length ? `Recent failures:\n${input.failedTraces.slice(0, 5).join('\n')}\n` : '') +
|
|
47
|
+
`\n--- current file ---\n${input.parentCode}\n--- end ---\nReturn the improved full file.`;
|
|
48
|
+
let res;
|
|
49
|
+
try {
|
|
50
|
+
res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { Authorization: `Bearer ${apiKey()}`, 'Content-Type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
model: this.model,
|
|
55
|
+
messages: [{ role: 'system', content: sys }, { role: 'user', content: user }],
|
|
56
|
+
max_tokens: this.maxTokens,
|
|
57
|
+
temperature: this.temperature,
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
// Network failure → safe no-op (return parent unchanged; the gate sees identity).
|
|
63
|
+
return { code: input.parentCode, summary: `openrouter:${this.model} unreachable (${e.message}) — no-op` };
|
|
64
|
+
}
|
|
65
|
+
const j = await res.json();
|
|
66
|
+
if (!j.choices?.[0]?.message?.content) {
|
|
67
|
+
return { code: input.parentCode, summary: `openrouter:${this.model} no content — no-op` };
|
|
68
|
+
}
|
|
69
|
+
this.telemetry.calls += 1;
|
|
70
|
+
if (j.usage) {
|
|
71
|
+
this.telemetry.promptTokens += j.usage.prompt_tokens ?? 0;
|
|
72
|
+
this.telemetry.completionTokens += j.usage.completion_tokens ?? 0;
|
|
73
|
+
this.telemetry.costUSD += j.usage.cost ?? 0;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
code: unfence(j.choices[0].message.content),
|
|
77
|
+
summary: `openrouter:${this.model} regenerated ${input.surface}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=openrouter-mutator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-mutator.js","sourceRoot":"","sources":["../src/openrouter-mutator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RepoProfile } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Profile a repository at `root`. Walks the tree (skipping node_modules, .git,
|
|
4
|
+
* .metaharness, dist), collects source/doc/json files, reads package.json if
|
|
5
|
+
* present for tooling, and flags risk files. Never throws on an unreadable tree.
|
|
6
|
+
*/
|
|
7
|
+
export declare function profileRepo(root: string): Promise<RepoProfile>;
|
|
8
|
+
//# sourceMappingURL=repo_profiler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo_profiler.d.ts","sourceRoot":"","sources":["../src/repo_profiler.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA4F9C;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA6BpE"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Repo profiler (ADR-070 §profile) — distil a repository into the small set of
|
|
4
|
+
// signals Darwin Mode needs: which files exist, how to run its tests, which
|
|
5
|
+
// package manager it uses, and which files are too risky to ever touch.
|
|
6
|
+
//
|
|
7
|
+
// Dependency-free (Node built-ins only). The walk is resilient: an unreadable
|
|
8
|
+
// directory is skipped rather than fatal, so a hostile or partial tree cannot
|
|
9
|
+
// abort a profile.
|
|
10
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
11
|
+
import { join, relative, sep } from 'node:path';
|
|
12
|
+
/** Directories never descended into during a profile walk. */
|
|
13
|
+
const SKIP_DIRS = new Set([
|
|
14
|
+
'node_modules',
|
|
15
|
+
'.git',
|
|
16
|
+
'.metaharness',
|
|
17
|
+
'dist',
|
|
18
|
+
]);
|
|
19
|
+
/** File extensions collected by the profiler. */
|
|
20
|
+
const COLLECT_EXTENSIONS = [
|
|
21
|
+
'.ts',
|
|
22
|
+
'.tsx',
|
|
23
|
+
'.js',
|
|
24
|
+
'.jsx',
|
|
25
|
+
'.json',
|
|
26
|
+
'.md',
|
|
27
|
+
];
|
|
28
|
+
/** Paths that look like they hold deployment, infra, or sensitive material. */
|
|
29
|
+
const RISK_PATTERN = /(\.env|secret|credential|token|key|deploy|release|infra)/i;
|
|
30
|
+
/** True if a relative path should be collected (by extension). */
|
|
31
|
+
function isCollectable(relPath) {
|
|
32
|
+
const lower = relPath.toLowerCase();
|
|
33
|
+
return COLLECT_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Recursively walk `dir`, pushing collectable file paths (relative to `root`)
|
|
37
|
+
* into `out`. Unreadable directories are skipped silently.
|
|
38
|
+
*/
|
|
39
|
+
async function walk(root, dir, out) {
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return; // unreadable directory — skip, do not fail the profile
|
|
46
|
+
}
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
const name = entry.name;
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
if (SKIP_DIRS.has(name))
|
|
51
|
+
continue;
|
|
52
|
+
await walk(root, join(dir, name), out);
|
|
53
|
+
}
|
|
54
|
+
else if (entry.isFile()) {
|
|
55
|
+
const rel = relative(root, join(dir, name));
|
|
56
|
+
if (isCollectable(rel))
|
|
57
|
+
out.push(rel.split(sep).join('/'));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the package manager and test command from a parsed package.json.
|
|
63
|
+
* Falls back to 'unknown' / 'npm test' when fields are absent or malformed.
|
|
64
|
+
*/
|
|
65
|
+
function resolveTooling(pkg) {
|
|
66
|
+
let packageManager = 'unknown';
|
|
67
|
+
let hasTestScript = false;
|
|
68
|
+
if (pkg && typeof pkg === 'object') {
|
|
69
|
+
const obj = pkg;
|
|
70
|
+
const pm = obj.packageManager;
|
|
71
|
+
if (typeof pm === 'string') {
|
|
72
|
+
if (pm.startsWith('pnpm'))
|
|
73
|
+
packageManager = 'pnpm';
|
|
74
|
+
else if (pm.startsWith('yarn'))
|
|
75
|
+
packageManager = 'yarn';
|
|
76
|
+
else if (pm.startsWith('npm'))
|
|
77
|
+
packageManager = 'npm';
|
|
78
|
+
}
|
|
79
|
+
const scripts = obj.scripts;
|
|
80
|
+
if (scripts && typeof scripts === 'object') {
|
|
81
|
+
const test = scripts.test;
|
|
82
|
+
if (typeof test === 'string' && test.trim().length > 0)
|
|
83
|
+
hasTestScript = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
let testCommand = 'npm test';
|
|
87
|
+
if (hasTestScript) {
|
|
88
|
+
const runner = packageManager === 'pnpm'
|
|
89
|
+
? 'pnpm'
|
|
90
|
+
: packageManager === 'yarn'
|
|
91
|
+
? 'yarn'
|
|
92
|
+
: 'npm';
|
|
93
|
+
testCommand = `${runner} test`;
|
|
94
|
+
}
|
|
95
|
+
return { packageManager, testCommand };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Profile a repository at `root`. Walks the tree (skipping node_modules, .git,
|
|
99
|
+
* .metaharness, dist), collects source/doc/json files, reads package.json if
|
|
100
|
+
* present for tooling, and flags risk files. Never throws on an unreadable tree.
|
|
101
|
+
*/
|
|
102
|
+
export async function profileRepo(root) {
|
|
103
|
+
const sourceFiles = [];
|
|
104
|
+
await walk(root, root, sourceFiles);
|
|
105
|
+
sourceFiles.sort();
|
|
106
|
+
let pkg = null;
|
|
107
|
+
try {
|
|
108
|
+
const raw = await readFile(join(root, 'package.json'), 'utf8');
|
|
109
|
+
pkg = JSON.parse(raw);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
pkg = null; // no package.json, or it is unreadable / malformed
|
|
113
|
+
}
|
|
114
|
+
const { packageManager, testCommand } = resolveTooling(pkg);
|
|
115
|
+
const riskFiles = sourceFiles.filter((f) => RISK_PATTERN.test(f));
|
|
116
|
+
const summary = `${sourceFiles.length} files, ${packageManager} package manager, ` +
|
|
117
|
+
`test via "${testCommand}", ${riskFiles.length} risk file(s)`;
|
|
118
|
+
return {
|
|
119
|
+
root,
|
|
120
|
+
packageManager,
|
|
121
|
+
testCommand,
|
|
122
|
+
sourceFiles,
|
|
123
|
+
riskFiles,
|
|
124
|
+
summary,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=repo_profiler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo_profiler.js","sourceRoot":"","sources":["../src/repo_profiler.ts"],"names":[],"mappings":"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"}
|
package/dist/safety.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { MutationSurface } from './types.js';
|
|
2
|
+
/** The seven approved mutation surfaces, in canonical order (ADR-071). */
|
|
3
|
+
export declare const SURFACES: readonly MutationSurface[];
|
|
4
|
+
/** Surface → the single file it owns. The ONLY files a variant may contain. */
|
|
5
|
+
export declare const FILE_BY_SURFACE: Readonly<Record<MutationSurface, string>>;
|
|
6
|
+
/** The exact set of filenames permitted inside a variant directory. */
|
|
7
|
+
export declare const APPROVED_FILES: ReadonlySet<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Blocked filename substrings (case-insensitive). A variant directory must never
|
|
10
|
+
* contain a file whose name hints at secrets, VCS, or keys (ADR-071).
|
|
11
|
+
*/
|
|
12
|
+
export declare const BLOCKED_FILENAME_PATTERNS: readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* Blocked code-content patterns (case-insensitive). If a variant file's text
|
|
15
|
+
* matches any of these, the variant is disqualified. This is intentionally
|
|
16
|
+
* broad: a harness mutation surface is pure policy logic — it has no business
|
|
17
|
+
* spawning processes, touching the network, reading the environment, the file
|
|
18
|
+
* system, or evaluating dynamic code.
|
|
19
|
+
*/
|
|
20
|
+
export declare const BLOCKED_CONTENT_PATTERNS: ReadonlyArray<{
|
|
21
|
+
re: RegExp;
|
|
22
|
+
reason: string;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Statically inspect a variant directory BEFORE it is allowed to run.
|
|
26
|
+
* Returns a list of blocking findings; an empty list means the variant is clean.
|
|
27
|
+
*
|
|
28
|
+
* Disqualifying conditions:
|
|
29
|
+
* - a nested directory, a symlink, or any non-regular-file entry;
|
|
30
|
+
* - a file that is not one of the seven approved filenames;
|
|
31
|
+
* - a filename matching a blocked pattern;
|
|
32
|
+
* - a file exceeding the size cap, or the directory exceeding the file cap;
|
|
33
|
+
* - file content matching a blocked-capability pattern.
|
|
34
|
+
*/
|
|
35
|
+
export declare function inspectVariant(dir: string): Promise<string[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Validate LLM/agent-generated code BEFORE it is written to a variant file.
|
|
38
|
+
* Independent of inspectVariant (defense in depth). Returns a list of violations;
|
|
39
|
+
* an empty list means the generated code is admissible. A generation that
|
|
40
|
+
* violates this is DISCARDED, never repaired in place (ADR-071).
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateGeneratedCode(code: string): string[];
|
|
43
|
+
/** Convenience: a variant is admissible iff inspectVariant finds nothing. */
|
|
44
|
+
export declare function isVariantSafe(dir: string): Promise<boolean>;
|
|
45
|
+
//# sourceMappingURL=safety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../src/safety.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,0EAA0E;AAC1E,eAAO,MAAM,QAAQ,EAAE,SAAS,eAAe,EAQrC,CAAC;AAEX,+EAA+E;AAC/E,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAQrE,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,MAAM,CAE9C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAatD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAwBlF,CAAC;AAMF;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAkEnE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAO5D;AAED,6EAA6E;AAC7E,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEjE"}
|
package/dist/safety.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// The safety layer (ADR-071) — the load-bearing security boundary of Darwin Mode.
|
|
4
|
+
//
|
|
5
|
+
// A self-modifying agent that can edit anything is a liability. Darwin Mode's
|
|
6
|
+
// bound is enforced HERE, with two independent, defense-in-depth checks:
|
|
7
|
+
//
|
|
8
|
+
// inspectVariant(dir) — runs BEFORE any variant executes. Disqualifies a
|
|
9
|
+
// variant whose directory contains anything other
|
|
10
|
+
// than the seven approved files, a blocked filename,
|
|
11
|
+
// a symlink, or blocked content.
|
|
12
|
+
// validateGeneratedCode() — runs BEFORE generated code is written to disk
|
|
13
|
+
// (the LLM-mutator path). Independent pattern set.
|
|
14
|
+
//
|
|
15
|
+
// Both are CODE, not comments. The gate precedes execution; a disqualified
|
|
16
|
+
// variant never has its test command run (sandbox returns exitCode 99). A
|
|
17
|
+
// variant with any blocked action scores safetyScore 0 and cannot be promoted
|
|
18
|
+
// (ADR-072 requires safetyScore ≥ 0.95).
|
|
19
|
+
import { lstat, readdir, readFile } from 'node:fs/promises';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
/** The seven approved mutation surfaces, in canonical order (ADR-071). */
|
|
22
|
+
export const SURFACES = [
|
|
23
|
+
'planner',
|
|
24
|
+
'contextBuilder',
|
|
25
|
+
'reviewer',
|
|
26
|
+
'retryPolicy',
|
|
27
|
+
'toolPolicy',
|
|
28
|
+
'memoryPolicy',
|
|
29
|
+
'scorePolicy',
|
|
30
|
+
];
|
|
31
|
+
/** Surface → the single file it owns. The ONLY files a variant may contain. */
|
|
32
|
+
export const FILE_BY_SURFACE = {
|
|
33
|
+
planner: 'planner.ts',
|
|
34
|
+
contextBuilder: 'context_builder.ts',
|
|
35
|
+
reviewer: 'reviewer.ts',
|
|
36
|
+
retryPolicy: 'retry_policy.ts',
|
|
37
|
+
toolPolicy: 'tool_policy.ts',
|
|
38
|
+
memoryPolicy: 'memory_policy.ts',
|
|
39
|
+
scorePolicy: 'score_policy.ts',
|
|
40
|
+
};
|
|
41
|
+
/** The exact set of filenames permitted inside a variant directory. */
|
|
42
|
+
export const APPROVED_FILES = new Set(Object.values(FILE_BY_SURFACE));
|
|
43
|
+
/**
|
|
44
|
+
* Blocked filename substrings (case-insensitive). A variant directory must never
|
|
45
|
+
* contain a file whose name hints at secrets, VCS, or keys (ADR-071).
|
|
46
|
+
*/
|
|
47
|
+
export const BLOCKED_FILENAME_PATTERNS = [
|
|
48
|
+
'.env',
|
|
49
|
+
'secret',
|
|
50
|
+
'credential',
|
|
51
|
+
'token',
|
|
52
|
+
'private_key',
|
|
53
|
+
'id_rsa',
|
|
54
|
+
'.git',
|
|
55
|
+
'.npmrc',
|
|
56
|
+
'package.json',
|
|
57
|
+
'package-lock',
|
|
58
|
+
'yarn.lock',
|
|
59
|
+
'pnpm-lock',
|
|
60
|
+
];
|
|
61
|
+
/**
|
|
62
|
+
* Blocked code-content patterns (case-insensitive). If a variant file's text
|
|
63
|
+
* matches any of these, the variant is disqualified. This is intentionally
|
|
64
|
+
* broad: a harness mutation surface is pure policy logic — it has no business
|
|
65
|
+
* spawning processes, touching the network, reading the environment, the file
|
|
66
|
+
* system, or evaluating dynamic code.
|
|
67
|
+
*/
|
|
68
|
+
export const BLOCKED_CONTENT_PATTERNS = [
|
|
69
|
+
// Environment access — both dotted (`process.env`) and computed-member forms
|
|
70
|
+
// (`process['env']`, `process[`env`]`, `process . env`). The char class
|
|
71
|
+
// `[.[]` covers the dot and the opening bracket; `['"\x60]?` the optional quote.
|
|
72
|
+
{ re: /process\s*[.[]\s*['"\x60]?\s*env/i, reason: 'environment access (process.env)' },
|
|
73
|
+
{ re: /\bReflect\s*\.\s*get\s*\(\s*process/i, reason: 'environment access (Reflect.get(process))' },
|
|
74
|
+
{ re: /process\s*[.[]\s*['"\x60]?\s*binding/i, reason: 'process.binding' },
|
|
75
|
+
{ re: /\bchild_process\b/i, reason: 'process spawning (child_process)' },
|
|
76
|
+
{ re: /\bexecSync\b|\bexecFileSync\b|\bspawnSync\b|\bspawn\b|\bexec\s*\(/i, reason: 'process execution' },
|
|
77
|
+
{ re: /\brequire\s*\(/i, reason: 'dynamic require()' },
|
|
78
|
+
{ re: /\bimport\s*\(/i, reason: 'dynamic import()' },
|
|
79
|
+
{ re: /\beval\s*\(/i, reason: 'eval()' },
|
|
80
|
+
{ re: /\bnew\s+Function\b/i, reason: 'new Function()' },
|
|
81
|
+
{ re: /\bfetch\s*\(/i, reason: 'network access (fetch)' },
|
|
82
|
+
{ re: /\bXMLHttpRequest\b|\bWebSocket\b/i, reason: 'network access (XHR/WebSocket)' },
|
|
83
|
+
// node: builtins, including subpaths like `node:fs/promises`.
|
|
84
|
+
{ re: /\bnode:(fs|net|http|https|dns|tls|dgram|cluster|vm|worker_threads)(\/|\b)/i, reason: 'restricted node builtin' },
|
|
85
|
+
// Bare-specifier imports, including subpaths like `fs/promises`, `net/x`.
|
|
86
|
+
{ re: /from\s+['"](fs|net|http|https|dns|tls|dgram|cluster|vm|worker_threads)(\/[^'"]*)?['"]/i, reason: 'restricted module import' },
|
|
87
|
+
{ re: /\bglobalThis\b|\b__proto__\b|\bconstructor\s*\[/i, reason: 'prototype/global escape' },
|
|
88
|
+
{ re: /\bcurl\b|\bwget\b|\bssh\b|\bscp\b|\bsudo\b|\bchmod\b|\bnc\s|\bnetcat\b/i, reason: 'shell command string' },
|
|
89
|
+
// Destructive fs — any `rm` with a flag OR a path (`rm -rf`, `rm -r`, `rm /etc/x`).
|
|
90
|
+
{ re: /\brm\s+[-/]|\brmdir\b|\bunlink\b|\brmSync\b/i, reason: 'destructive filesystem command' },
|
|
91
|
+
{ re: /\bprivate_key\b|\bcredential\b|\bsecret\b|\btoken\b|BEGIN [A-Z ]*PRIVATE KEY/i, reason: 'secret handling' },
|
|
92
|
+
];
|
|
93
|
+
/** Hard caps to bound a pathological variant directory. */
|
|
94
|
+
const MAX_FILES = 32;
|
|
95
|
+
const MAX_FILE_BYTES = 256 * 1024;
|
|
96
|
+
/**
|
|
97
|
+
* Statically inspect a variant directory BEFORE it is allowed to run.
|
|
98
|
+
* Returns a list of blocking findings; an empty list means the variant is clean.
|
|
99
|
+
*
|
|
100
|
+
* Disqualifying conditions:
|
|
101
|
+
* - a nested directory, a symlink, or any non-regular-file entry;
|
|
102
|
+
* - a file that is not one of the seven approved filenames;
|
|
103
|
+
* - a filename matching a blocked pattern;
|
|
104
|
+
* - a file exceeding the size cap, or the directory exceeding the file cap;
|
|
105
|
+
* - file content matching a blocked-capability pattern.
|
|
106
|
+
*/
|
|
107
|
+
export async function inspectVariant(dir) {
|
|
108
|
+
const blocked = [];
|
|
109
|
+
let entries;
|
|
110
|
+
try {
|
|
111
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return [`variant directory unreadable: ${dir}`];
|
|
115
|
+
}
|
|
116
|
+
if (entries.length > MAX_FILES) {
|
|
117
|
+
blocked.push(`too many entries (${entries.length} > ${MAX_FILES})`);
|
|
118
|
+
}
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const name = entry.name;
|
|
121
|
+
const full = join(dir, name);
|
|
122
|
+
// Reject symlinks and non-regular files via lstat (no symlink escape).
|
|
123
|
+
let stat;
|
|
124
|
+
try {
|
|
125
|
+
stat = await lstat(full);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
blocked.push(`unstattable entry ${name}`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (stat.isSymbolicLink()) {
|
|
132
|
+
blocked.push(`symlink not allowed: ${name}`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (stat.isDirectory()) {
|
|
136
|
+
blocked.push(`unexpected directory ${name}`);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (!stat.isFile()) {
|
|
140
|
+
blocked.push(`non-regular file ${name}`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const lower = name.toLowerCase();
|
|
144
|
+
if (!APPROVED_FILES.has(name)) {
|
|
145
|
+
blocked.push(`unexpected file ${name} (not in the approved allowlist)`);
|
|
146
|
+
}
|
|
147
|
+
for (const pat of BLOCKED_FILENAME_PATTERNS) {
|
|
148
|
+
if (lower.includes(pat)) {
|
|
149
|
+
blocked.push(`blocked filename pattern "${pat}" in ${name}`);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (stat.size > MAX_FILE_BYTES) {
|
|
154
|
+
blocked.push(`file ${name} too large (${stat.size} > ${MAX_FILE_BYTES} bytes)`);
|
|
155
|
+
continue; // do not read an oversized file into memory
|
|
156
|
+
}
|
|
157
|
+
let content;
|
|
158
|
+
try {
|
|
159
|
+
content = await readFile(full, 'utf8');
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
blocked.push(`unreadable file ${name}`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
for (const { re, reason } of BLOCKED_CONTENT_PATTERNS) {
|
|
166
|
+
if (re.test(content))
|
|
167
|
+
blocked.push(`blocked content in ${name}: ${reason}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return blocked;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Validate LLM/agent-generated code BEFORE it is written to a variant file.
|
|
174
|
+
* Independent of inspectVariant (defense in depth). Returns a list of violations;
|
|
175
|
+
* an empty list means the generated code is admissible. A generation that
|
|
176
|
+
* violates this is DISCARDED, never repaired in place (ADR-071).
|
|
177
|
+
*/
|
|
178
|
+
export function validateGeneratedCode(code) {
|
|
179
|
+
const violations = [];
|
|
180
|
+
for (const { re, reason } of BLOCKED_CONTENT_PATTERNS) {
|
|
181
|
+
if (re.test(code))
|
|
182
|
+
violations.push(reason);
|
|
183
|
+
}
|
|
184
|
+
// De-duplicate (a pattern set can flag the same reason twice).
|
|
185
|
+
return [...new Set(violations)];
|
|
186
|
+
}
|
|
187
|
+
/** Convenience: a variant is admissible iff inspectVariant finds nothing. */
|
|
188
|
+
export async function isVariantSafe(dir) {
|
|
189
|
+
return (await inspectVariant(dir)).length === 0;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../src/safety.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,kFAAkF;AAClF,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AACzE,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,kFAAkF;AAClF,8DAA8D;AAC9D,6EAA6E;AAC7E,gFAAgF;AAChF,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,8EAA8E;AAC9E,yCAAyC;AAEzC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,QAAQ,GAA+B;IAClD,SAAS;IACT,gBAAgB;IAChB,UAAU;IACV,aAAa;IACb,YAAY;IACZ,cAAc;IACd,aAAa;CACL,CAAC;AAEX,+EAA+E;AAC/E,MAAM,CAAC,MAAM,eAAe,GAA8C;IACxE,OAAO,EAAE,YAAY;IACrB,cAAc,EAAE,oBAAoB;IACpC,QAAQ,EAAE,aAAa;IACvB,WAAW,EAAE,iBAAiB;IAC9B,UAAU,EAAE,gBAAgB;IAC5B,YAAY,EAAE,kBAAkB;IAChC,WAAW,EAAE,iBAAiB;CAC/B,CAAC;AAEF,uEAAuE;AACvE,MAAM,CAAC,MAAM,cAAc,GAAwB,IAAI,GAAG,CACxD,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAC/B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAsB;IAC1D,MAAM;IACN,QAAQ;IACR,YAAY;IACZ,OAAO;IACP,aAAa;IACb,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,cAAc;IACd,cAAc;IACd,WAAW;IACX,WAAW;CACZ,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAkD;IACrF,6EAA6E;IAC7E,wEAAwE;IACxE,iFAAiF;IACjF,EAAE,EAAE,EAAE,mCAAmC,EAAE,MAAM,EAAE,kCAAkC,EAAE;IACvF,EAAE,EAAE,EAAE,sCAAsC,EAAE,MAAM,EAAE,2CAA2C,EAAE;IACnG,EAAE,EAAE,EAAE,uCAAuC,EAAE,MAAM,EAAE,iBAAiB,EAAE;IAC1E,EAAE,EAAE,EAAE,oBAAoB,EAAE,MAAM,EAAE,kCAAkC,EAAE;IACxE,EAAE,EAAE,EAAE,oEAAoE,EAAE,MAAM,EAAE,mBAAmB,EAAE;IACzG,EAAE,EAAE,EAAE,iBAAiB,EAAE,MAAM,EAAE,mBAAmB,EAAE;IACtD,EAAE,EAAE,EAAE,gBAAgB,EAAE,MAAM,EAAE,kBAAkB,EAAE;IACpD,EAAE,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE;IACxC,EAAE,EAAE,EAAE,qBAAqB,EAAE,MAAM,EAAE,gBAAgB,EAAE;IACvD,EAAE,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,wBAAwB,EAAE;IACzD,EAAE,EAAE,EAAE,mCAAmC,EAAE,MAAM,EAAE,gCAAgC,EAAE;IACrF,8DAA8D;IAC9D,EAAE,EAAE,EAAE,4EAA4E,EAAE,MAAM,EAAE,yBAAyB,EAAE;IACvH,0EAA0E;IAC1E,EAAE,EAAE,EAAE,wFAAwF,EAAE,MAAM,EAAE,0BAA0B,EAAE;IACpI,EAAE,EAAE,EAAE,kDAAkD,EAAE,MAAM,EAAE,yBAAyB,EAAE;IAC7F,EAAE,EAAE,EAAE,yEAAyE,EAAE,MAAM,EAAE,sBAAsB,EAAE;IACjH,oFAAoF;IACpF,EAAE,EAAE,EAAE,8CAA8C,EAAE,MAAM,EAAE,gCAAgC,EAAE;IAChG,EAAE,EAAE,EAAE,+EAA+E,EAAE,MAAM,EAAE,iBAAiB,EAAE;CACnH,CAAC;AAEF,2DAA2D;AAC3D,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC;AAElC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC,qBAAqB,OAAO,CAAC,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAE7B,uEAAuE;QACvE,IAAI,IAAI,CAAC;QACT,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC;YAC1C,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;YAC7C,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;YAC7C,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;YACzC,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,mBAAmB,IAAI,kCAAkC,CAAC,CAAC;QAC1E,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,yBAAyB,EAAE,CAAC;YAC5C,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,CAAC,IAAI,CAAC,6BAA6B,GAAG,QAAQ,IAAI,EAAE,CAAC,CAAC;gBAC7D,MAAM;YACR,CAAC;QACH,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,GAAG,cAAc,EAAE,CAAC;YAC/B,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,eAAe,IAAI,CAAC,IAAI,MAAM,cAAc,SAAS,CAAC,CAAC;YAChF,SAAS,CAAC,4CAA4C;QACxD,CAAC;QAED,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;YACxC,SAAS;QACX,CAAC;QACD,KAAK,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,wBAAwB,EAAE,CAAC;YACtD,IAAI,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,sBAAsB,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,wBAAwB,EAAE,CAAC;QACtD,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IACD,+DAA+D;IAC/D,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,OAAO,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HarnessVariant, RepoProfile, RunTrace } from './types.js';
|
|
2
|
+
/** Tunables for one sandboxed run. */
|
|
3
|
+
export interface SandboxOptions {
|
|
4
|
+
/** Wall-clock budget for the test command (ms). Default 120000. */
|
|
5
|
+
taskTimeoutMs?: number;
|
|
6
|
+
/** Max bytes of combined stdout/stderr to buffer. Default 8 MiB. */
|
|
7
|
+
maxBufferBytes?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run one variant against one task in the sandbox.
|
|
11
|
+
*
|
|
12
|
+
* The ADR-071 safety gate runs first: if `inspectVariant` reports any findings,
|
|
13
|
+
* no command is executed and a disqualified trace (exitCode 99) is returned.
|
|
14
|
+
* Otherwise the profile's `testCommand` is executed via `execFile` (no shell)
|
|
15
|
+
* with a scrubbed env. Never throws — failures become RunTraces.
|
|
16
|
+
*/
|
|
17
|
+
export declare function runVariantTask(variant: HarnessVariant, profile: RepoProfile, taskId: string, opts?: SandboxOptions): Promise<RunTrace>;
|
|
18
|
+
/**
|
|
19
|
+
* Run a variant against a list of tasks sequentially, returning every trace.
|
|
20
|
+
* Sequential by design: it bounds resource use and keeps traces deterministic
|
|
21
|
+
* (the population-level concurrency budget lives in the evolution loop, not here).
|
|
22
|
+
*/
|
|
23
|
+
export declare function runVariantTasks(variant: HarnessVariant, profile: RepoProfile, taskIds: string[], opts?: SandboxOptions): Promise<RunTrace[]>;
|
|
24
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAaxE,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAmCD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,QAAQ,CAAC,CAmFnB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAMrB"}
|