@evomap/evolver-core 2.0.0-beta.9 → 2.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/dist/algo/antiDistill.js +1 -167
- package/dist/algo/bans.js +1 -33
- package/dist/algo/candidateAssembly.d.ts +7 -2
- package/dist/algo/candidateAssembly.js +1 -205
- package/dist/algo/capabilityCandidates.js +1 -136
- package/dist/algo/confidence.js +1 -196
- package/dist/algo/conversationSniffer.js +1 -149
- package/dist/algo/cycleEngine.d.ts +23 -2
- package/dist/algo/cycleEngine.js +1 -425
- package/dist/algo/cycleFailureClassifier.js +1 -112
- package/dist/algo/epigenetics.js +1 -41
- package/dist/algo/evolutionEvent.js +1 -24
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +1 -55
- package/dist/algo/geneHealth.d.ts +2 -2
- package/dist/algo/geneHealth.js +1 -16
- package/dist/algo/geneIntake.js +1 -128
- package/dist/algo/genePromotion.js +1 -53
- package/dist/algo/geneSelection.d.ts +63 -5
- package/dist/algo/geneSelection.js +1 -246
- package/dist/algo/index.d.ts +2 -0
- package/dist/algo/index.js +1 -20
- package/dist/algo/memoryGraph.js +1 -86
- package/dist/algo/mutation.js +1 -22
- package/dist/algo/orchestrator.d.ts +8 -1
- package/dist/algo/orchestrator.js +1 -75
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +1 -0
- package/dist/algo/solidify.js +1 -63
- package/dist/algo/strategyPresets.js +1 -61
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +1 -0
- package/dist/assetstore/assetSidecarRecords.d.ts +7 -2
- package/dist/assetstore/assetSidecarRecords.js +238 -0
- package/dist/assetstore/assetSidecarRecovery.js +2 -2
- package/dist/assetstore/assetStoreHealth.d.ts +6 -0
- package/dist/assetstore/assetStoreHealth.js +56 -14
- package/dist/assetstore/assetStoreStorage.d.ts +1 -1
- package/dist/assetstore/assetStoreStorage.js +27 -9
- package/dist/assetstore/assetSyncLedger.d.ts +86 -0
- package/dist/assetstore/assetSyncLedger.js +683 -4
- package/dist/assetstore/index.d.ts +1 -0
- package/dist/assetstore/index.js +1 -0
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +4 -2
- package/dist/assetstore/localJsonl.js +89 -9
- package/dist/assetstore/provenance.d.ts +80 -4
- package/dist/assetstore/provenance.js +313 -5
- package/dist/assetstore/provider.d.ts +58 -1
- package/dist/assetstore/provider.js +97 -6
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +1 -251
- package/dist/benchmark/antiGeneImpact.js +1 -34
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +1 -268
- package/dist/benchmark/benchmark.js +1 -26
- package/dist/benchmark/evolutionThesisSolver.js +1 -41
- package/dist/benchmark/index.d.ts +2 -1
- package/dist/benchmark/index.js +1 -6
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +1 -0
- package/dist/benchmark/thesis.js +1 -150
- package/dist/benchmark/triggerShift.js +1 -106
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +2 -1
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/v1EnvCompat.d.ts +111 -0
- package/dist/bootstrap/v1EnvCompat.js +280 -0
- package/dist/cycle/cycleTimeline.js +1 -74
- package/dist/cycle/index.js +1 -2
- package/dist/cycle/stateMachine.js +1 -25
- package/dist/events/eventArchive.d.ts +2 -0
- package/dist/events/eventArchive.js +13 -3
- package/dist/events/eventSchema.d.ts +7 -7
- package/dist/events/eventStore.d.ts +2 -0
- package/dist/events/eventStore.js +5 -1
- package/dist/events/ingest.d.ts +1 -0
- package/dist/events/ingest.js +1 -0
- package/dist/events/paths.d.ts +3 -1
- package/dist/events/paths.js +4 -0
- package/dist/events/public.d.ts +2 -2
- package/dist/events/public.js +2 -2
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +43 -2
- package/dist/exec/autoExec.js +76 -9
- package/dist/exec/autonomousCycle.d.ts +22 -4
- package/dist/exec/autonomousCycle.js +64 -13
- package/dist/exec/claudeBridge.d.ts +32 -7
- package/dist/exec/claudeBridge.js +281 -29
- package/dist/exec/prompt.js +5 -1
- package/dist/exec/runnerRegistry.d.ts +68 -26
- package/dist/exec/runnerRegistry.js +307 -72
- package/dist/exec/selfPr.js +1 -7
- package/dist/feedback/envelope.d.ts +61 -0
- package/dist/feedback/envelope.js +168 -0
- package/dist/feedback/index.d.ts +1 -0
- package/dist/feedback/index.js +1 -0
- package/dist/hooks/hooks.js +1 -0
- package/dist/hub/agentDirectory.js +1 -104
- package/dist/hub/assetCallLog.d.ts +35 -1
- package/dist/hub/assetCallLog.js +1 -72
- package/dist/hub/bindings.d.ts +8 -1
- package/dist/hub/bindings.js +1 -110
- package/dist/hub/capability.d.ts +90 -3
- package/dist/hub/capability.js +1 -1
- package/dist/hub/conversationDistiller.js +1 -264
- package/dist/hub/fake.d.ts +2 -2
- package/dist/hub/fake.js +1 -70
- package/dist/hub/hubReview.js +1 -106
- package/dist/hub/index.js +1 -11
- package/dist/hub/ingest.js +1 -15
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +1 -406
- package/dist/hub/reuseDecision.js +1 -127
- package/dist/hub/sanitize.js +1 -322
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/mailbox/dispatch.d.ts +1 -1
- package/dist/mailbox/dispatch.js +22 -6
- package/dist/mailbox/envelope.d.ts +7 -1
- package/dist/mailbox/envelope.js +9 -2
- package/dist/mailbox/ipcServer.d.ts +10 -2
- package/dist/mailbox/ipcServer.js +163 -13
- package/dist/mailbox/store.d.ts +89 -3
- package/dist/mailbox/store.js +895 -41
- package/dist/material/boundary.js +1 -14
- package/dist/material/consumer.js +1 -55
- package/dist/material/emit.js +1 -52
- package/dist/material/factory.js +1 -25
- package/dist/material/index.js +1 -9
- package/dist/material/materialArchive.js +1 -466
- package/dist/material/materialStore.js +1 -69
- package/dist/material/sampling.js +1 -39
- package/dist/material/sources.js +1 -33
- package/dist/material/watermark.js +1 -76
- package/dist/ops/evolutionGraphProjection.d.ts +20 -0
- package/dist/ops/evolutionGraphProjection.js +315 -0
- package/dist/ops/index.d.ts +2 -1
- package/dist/ops/index.js +2 -1
- package/dist/ops/selfUpdate.d.ts +8 -0
- package/dist/ops/selfUpdate.js +24 -8
- package/dist/personality/drift.js +1 -106
- package/dist/personality/events.js +1 -37
- package/dist/personality/evolveOps.js +1 -93
- package/dist/personality/index.js +1 -11
- package/dist/personality/mutate.js +1 -39
- package/dist/personality/pivot.js +1 -22
- package/dist/personality/prompt.js +1 -62
- package/dist/personality/riskGate.js +1 -80
- package/dist/personality/schema.d.ts +16 -16
- package/dist/personality/schema.js +1 -119
- package/dist/personality/select.js +1 -71
- package/dist/personality/stats.js +1 -82
- package/dist/personality/store.js +1 -92
- package/dist/schema/common.js +1 -17
- package/dist/schema/evolutionGraph.d.ts +784 -0
- package/dist/schema/evolutionGraph.js +1 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -5
- package/dist/schema/material.js +1 -50
- package/dist/schema/problem.js +1 -45
- package/dist/schema/proofOfWork.js +1 -9
- package/dist/schema/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -19
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +1 -0
- package/dist/signals/cycleHistoryFromEvents.js +1 -96
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +1 -125
- package/dist/signals/extractor.d.ts +2 -2
- package/dist/signals/extractor.js +1 -57
- package/dist/signals/index.d.ts +3 -1
- package/dist/signals/index.js +1 -6
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +1 -158
- package/dist/signals/signalGate.js +1 -40
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +1 -0
- package/dist/signals/traceSignals.js +1 -123
- package/dist/strategy/constraintAblation.js +1 -3074
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +1 -0
- package/dist/strategy/experiment.js +1 -63
- package/dist/strategy/index.js +1 -3
- package/dist/strategy/strategyPoint.js +1 -23
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +209 -0
- package/dist/trace/learningTrace.js +276 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/verify/sandboxRunner.d.ts +28 -0
- package/dist/verify/sandboxRunner.js +218 -19
- package/dist/verify/sandboxedValidation.d.ts +9 -0
- package/dist/verify/sandboxedValidation.js +113 -13
- package/dist/verify/validation.d.ts +11 -1
- package/dist/verify/validation.js +31 -0
- package/package.json +5 -2
|
@@ -12,19 +12,19 @@
|
|
|
12
12
|
// or EVOLVE_EXEC_BRIDGE === '1'. Wiring it in by accident must fail loudly rather than silently spawn an
|
|
13
13
|
// autonomous agent.
|
|
14
14
|
import { randomUUID } from 'node:crypto';
|
|
15
|
-
import { resolve as resolvePath, sep, join as joinPath } from 'node:path';
|
|
16
|
-
import { tmpdir } from 'node:os';
|
|
17
|
-
import { closeSync, lstatSync, mkdtempSync, openSync, realpathSync, rmdirSync, rmSync, writeFileSync, } from 'node:fs';
|
|
15
|
+
import { basename, dirname, resolve as resolvePath, sep, join as joinPath } from 'node:path';
|
|
16
|
+
import { homedir, tmpdir } from 'node:os';
|
|
17
|
+
import { closeSync, existsSync, lstatSync, mkdtempSync, openSync, readdirSync, realpathSync, rmdirSync, rmSync, writeFileSync, } from 'node:fs';
|
|
18
18
|
import { renderExecPrompt } from './prompt.js';
|
|
19
19
|
import { parseGitShortstat, gitDiffProof } from './proofOfWork.js';
|
|
20
20
|
// Policy enforcement core (#107): checkPolicy runs the always-on global guards (blast hard cap +
|
|
21
21
|
// protected paths + destructive deletes) on EVERY exec — gene or not — plus the per-gene constraints when a
|
|
22
22
|
// gene supplies them. It supersedes the old gene-gated `checkChangeConstraints` call (the no-gene-no-guard hole).
|
|
23
23
|
import { checkPolicy, summarizeViolations } from './policy/index.js';
|
|
24
|
-
import { spawnCapture, SpawnCaptureFinalizeError, getRunnerSpec, DEFAULT_TIMEOUT_MS } from './runnerRegistry.js';
|
|
24
|
+
import { spawnCapture, SpawnCaptureFinalizeError, getRunnerSpec, validateAgentSessionResume, DEFAULT_TIMEOUT_MS } from './runnerRegistry.js';
|
|
25
25
|
// Re-export the runner layer so existing importers of ./claudeBridge.js (and the `exec` namespace) keep their
|
|
26
26
|
// surface after the #91-6 split — the registry simply has a clearer home now.
|
|
27
|
-
export { resolveSpawnCommand, spawnCapture, DEFAULT_MAX_CAPTURE_BYTES, UnboundedSkipPermissionsError, UnsupportedCursorSkipPermissionsError, UnsupportedGeminiPermissionOptionsError, claudeRunnerArgs, makeClaudeHeadlessRunner, claudeHeadlessRunner, codexRunnerArgs, makeCodexHeadlessRunner, cursorRunnerArgs, makeCursorHeadlessRunner, getRunnerSpec, geminiRunnerArgs, makeGeminiHeadlessRunner, classifyGeminiRunnerResult, } from './runnerRegistry.js';
|
|
27
|
+
export { resolveSpawnCommand, spawnCapture, DEFAULT_MAX_CAPTURE_BYTES, MAX_AGENT_SESSION_ID_CHARS, AgentSessionResumeError, validateAgentSessionResume, UnboundedSkipPermissionsError, UnsupportedCodexPermissionOptionsError, UnsupportedCursorAllowedToolsError, UnsupportedCursorSkipPermissionsError, UnsupportedCursorWorkspaceTrustError, UnsupportedGeminiPermissionOptionsError, claudeRunnerArgs, makeClaudeHeadlessRunner, claudeHeadlessRunner, codexRunnerArgs, makeCodexHeadlessRunner, cursorRunnerArgs, makeCursorHeadlessRunner, getRunnerSpec, hasBoundedClaudeFileAccess, CLAUDE_SAFE_AUTONOMOUS_TOOLS, geminiRunnerArgs, makeGeminiHeadlessRunner, classifyGeminiRunnerResult, } from './runnerRegistry.js';
|
|
28
28
|
export class ExecBridgeDisabledError extends Error {
|
|
29
29
|
constructor() {
|
|
30
30
|
super('exec bridge is disabled — set EVOLVE_EXEC_BRIDGE=1 or pass { enabled: true } to enable agent execution');
|
|
@@ -42,12 +42,11 @@ export class ExecBridgeForbiddenError extends Error {
|
|
|
42
42
|
* Thrown when a FULL-ACCESS (or unverified) agent run is requested without worktree isolation. The throwaway
|
|
43
43
|
* worktree is the containment WE control — allowedRoots gates the cwd but cannot stop an auto-approved or
|
|
44
44
|
* unsandboxed process writing/running outside it. Gated runners:
|
|
45
|
-
* - codex
|
|
46
|
-
* workspace-write, so only the skip path is gated.
|
|
45
|
+
* - codex permission overrides are refused by the runner because it has no enforceable per-tool allowlist.
|
|
47
46
|
* - cursor default → gated UNCONDITIONALLY. cursor-agent base `-p` already documents write+shell access, cursor
|
|
48
47
|
* skipPermissions is rejected by the runner layer, and the scaffold remains unverified (#66/#181) — so default
|
|
49
48
|
* cursor still needs the wrapper worktree rather than risk mutating the real tree.
|
|
50
|
-
*
|
|
49
|
+
* Claude is fail-closed below because a tool-name allowlist does not constrain absolute filesystem paths.
|
|
51
50
|
*/
|
|
52
51
|
export class UnsandboxedFullAccessRequiresIsolationError extends Error {
|
|
53
52
|
constructor() {
|
|
@@ -55,6 +54,31 @@ export class UnsandboxedFullAccessRequiresIsolationError extends Error {
|
|
|
55
54
|
this.name = 'UnsandboxedFullAccessRequiresIsolationError';
|
|
56
55
|
}
|
|
57
56
|
}
|
|
57
|
+
export class WorkspaceTrustRequiresIsolationError extends Error {
|
|
58
|
+
code = 'WORKSPACE_TRUST_REQUIRES_ISOLATION';
|
|
59
|
+
constructor() {
|
|
60
|
+
super('workspaceTrust=isolated-worktree requires isolation=worktree');
|
|
61
|
+
this.name = 'WorkspaceTrustRequiresIsolationError';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class UnsupportedCursorBuiltInRunnerError extends Error {
|
|
65
|
+
constructor() {
|
|
66
|
+
super('built-in cursor autonomous execution is unsupported until host filesystem and network containment is verified');
|
|
67
|
+
this.name = 'UnsupportedCursorBuiltInRunnerError';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export class UnsupportedClaudeBuiltInRunnerError extends Error {
|
|
71
|
+
constructor() {
|
|
72
|
+
super('built-in claude autonomous execution is unsupported until host filesystem and network containment is verified');
|
|
73
|
+
this.name = 'UnsupportedClaudeBuiltInRunnerError';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export class UnsupportedCodexBuiltInRunnerError extends Error {
|
|
77
|
+
constructor() {
|
|
78
|
+
super('built-in codex autonomous execution is unsupported because workspace-write does not contain host filesystem reads');
|
|
79
|
+
this.name = 'UnsupportedCodexBuiltInRunnerError';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
58
82
|
export class UnsafeWorktreePathError extends Error {
|
|
59
83
|
constructor(reason) {
|
|
60
84
|
super(`worktree isolation refused: ${reason}`);
|
|
@@ -118,6 +142,80 @@ function worktreePathStillOwned(reservation, identity) {
|
|
|
118
142
|
return false;
|
|
119
143
|
}
|
|
120
144
|
}
|
|
145
|
+
async function verifyManagedCursorWorktree(path, expectedName, repoCwd, git, managedRoot) {
|
|
146
|
+
const resolvedPath = resolvePath(path);
|
|
147
|
+
const cursorRoot = realpathSync(managedRoot);
|
|
148
|
+
if (resolvedPath !== path) {
|
|
149
|
+
throw new UnsafeWorktreePathError('Cursor reported a non-canonical worktree path');
|
|
150
|
+
}
|
|
151
|
+
const realPath = realpathSync(resolvedPath);
|
|
152
|
+
if (!isWithinRoot(realPath, cursorRoot)) {
|
|
153
|
+
throw new UnsafeWorktreePathError('Cursor reported a worktree outside its managed root');
|
|
154
|
+
}
|
|
155
|
+
if (basename(realPath) !== expectedName) {
|
|
156
|
+
throw new UnsafeWorktreePathError('Cursor reported an unexpected worktree name');
|
|
157
|
+
}
|
|
158
|
+
const stat = lstatSync(resolvedPath);
|
|
159
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
160
|
+
throw new UnsafeWorktreePathError('Cursor worktree is not a real directory');
|
|
161
|
+
}
|
|
162
|
+
const [repoCommonDir, worktreeCommonDir] = await Promise.all([
|
|
163
|
+
git(['rev-parse', '--path-format=absolute', '--git-common-dir'], repoCwd),
|
|
164
|
+
git(['rev-parse', '--path-format=absolute', '--git-common-dir'], realPath),
|
|
165
|
+
]);
|
|
166
|
+
if (realpathSync(repoCommonDir.trim()) !== realpathSync(worktreeCommonDir.trim())) {
|
|
167
|
+
throw new UnsafeWorktreePathError('Cursor worktree belongs to a different repository');
|
|
168
|
+
}
|
|
169
|
+
return { path: realPath, dev: stat.dev, ino: stat.ino };
|
|
170
|
+
}
|
|
171
|
+
async function cleanupManagedCursorWorktree(identity, git, repoCwd) {
|
|
172
|
+
const stat = lstatSync(identity.path);
|
|
173
|
+
if (stat.isSymbolicLink() || !stat.isDirectory() || stat.dev !== identity.dev || stat.ino !== identity.ino) {
|
|
174
|
+
throw new UnsafeWorktreePathError('verified Cursor worktree path changed before cleanup');
|
|
175
|
+
}
|
|
176
|
+
await git(['worktree', 'remove', '--force', identity.path], repoCwd, undefined, { processSignalMode: 'ignore' });
|
|
177
|
+
assertPathAbsent(identity.path);
|
|
178
|
+
}
|
|
179
|
+
async function discoverManagedCursorWorktree(expectedName, repoCwd, git, managedRoot) {
|
|
180
|
+
const cursorRoot = managedRoot;
|
|
181
|
+
if (!existsSync(cursorRoot))
|
|
182
|
+
return undefined;
|
|
183
|
+
const verified = [];
|
|
184
|
+
let validationError;
|
|
185
|
+
for (const entry of readdirSync(cursorRoot, { withFileTypes: true })) {
|
|
186
|
+
if (!entry.isDirectory())
|
|
187
|
+
continue;
|
|
188
|
+
const candidate = joinPath(cursorRoot, entry.name, expectedName);
|
|
189
|
+
if (!existsSync(candidate))
|
|
190
|
+
continue;
|
|
191
|
+
try {
|
|
192
|
+
verified.push(await verifyManagedCursorWorktree(candidate, expectedName, repoCwd, git, cursorRoot));
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
// Unverified same-name paths are never read or removed.
|
|
196
|
+
validationError ??= error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (verified.length > 1) {
|
|
200
|
+
throw new UnsafeWorktreePathError('multiple verified Cursor worktrees matched the generated name');
|
|
201
|
+
}
|
|
202
|
+
if (verified.length === 0 && validationError)
|
|
203
|
+
throw validationError;
|
|
204
|
+
return verified[0];
|
|
205
|
+
}
|
|
206
|
+
async function resolveManagedCursorWorktree(reportedPath, expectedName, repoCwd, git, managedRoot) {
|
|
207
|
+
if (!reportedPath)
|
|
208
|
+
return discoverManagedCursorWorktree(expectedName, repoCwd, git, managedRoot);
|
|
209
|
+
try {
|
|
210
|
+
return await verifyManagedCursorWorktree(reportedPath, expectedName, repoCwd, git, managedRoot);
|
|
211
|
+
}
|
|
212
|
+
catch (reportedPathError) {
|
|
213
|
+
const discovered = await discoverManagedCursorWorktree(expectedName, repoCwd, git, managedRoot);
|
|
214
|
+
if (discovered)
|
|
215
|
+
return discovered;
|
|
216
|
+
throw reportedPathError;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
121
219
|
function removeEmptyReservation(reservation) {
|
|
122
220
|
try {
|
|
123
221
|
const stat = lstatSync(reservation.container);
|
|
@@ -161,11 +259,38 @@ async function cleanupWorktreeReservation(reservation, identity, git, repoCwd) {
|
|
|
161
259
|
}
|
|
162
260
|
removeEmptyReservation(reservation);
|
|
163
261
|
}
|
|
164
|
-
/**
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
262
|
+
/** Resolve existing path components so an allowlisted symlink cannot redirect execution outside its root. */
|
|
263
|
+
function canonicalPath(path) {
|
|
264
|
+
let current = resolvePath(path);
|
|
265
|
+
const missing = [];
|
|
266
|
+
while (true) {
|
|
267
|
+
try {
|
|
268
|
+
return resolvePath(realpathSync(current), ...missing.reverse());
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
const code = error.code;
|
|
272
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR')
|
|
273
|
+
return undefined;
|
|
274
|
+
const parent = dirname(current);
|
|
275
|
+
if (parent === current)
|
|
276
|
+
return undefined;
|
|
277
|
+
missing.push(basename(current));
|
|
278
|
+
current = parent;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** Whether `child` is the same as, or nested under, `root`, including filesystem symlink resolution. */
|
|
283
|
+
export function isWithinRoot(child, root) {
|
|
284
|
+
return canonicalPathWithinRoots(child, [root]) !== undefined;
|
|
285
|
+
}
|
|
286
|
+
function canonicalPathWithinRoots(child, roots) {
|
|
287
|
+
const c = canonicalPath(child);
|
|
288
|
+
if (!c)
|
|
289
|
+
return undefined;
|
|
290
|
+
return roots.some((root) => {
|
|
291
|
+
const r = canonicalPath(root);
|
|
292
|
+
return Boolean(r && (c === r || c.startsWith(r.endsWith(sep) ? r : r + sep)));
|
|
293
|
+
}) ? c : undefined;
|
|
169
294
|
}
|
|
170
295
|
/**
|
|
171
296
|
* Sensitive env keys to strip before spawning an agent/tool (finding #39.2): evolver/hub/cloud secrets must
|
|
@@ -237,6 +362,12 @@ class ExecBridgeRunCancelledError extends Error {
|
|
|
237
362
|
this.name = 'ExecBridgeRunCancelledError';
|
|
238
363
|
}
|
|
239
364
|
}
|
|
365
|
+
class AgentRunBeforeManagedWorktreeError extends Error {
|
|
366
|
+
constructor() {
|
|
367
|
+
super('agent run failed before a managed worktree was created');
|
|
368
|
+
this.name = 'AgentRunBeforeManagedWorktreeError';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
240
371
|
class GitProofError extends Error {
|
|
241
372
|
constructor(message) {
|
|
242
373
|
super(message);
|
|
@@ -268,6 +399,15 @@ function failedProofExecutionResult(run, error, proofOfWork) {
|
|
|
268
399
|
sessionLog: run.error ? `${run.output}\n${run.error}` : run.output,
|
|
269
400
|
};
|
|
270
401
|
}
|
|
402
|
+
function failedAgentRunResult(run) {
|
|
403
|
+
return {
|
|
404
|
+
outcome: { status: 'failed', score: 0.1, reason: run.error ?? run.failureKind ?? 'agent run failed' },
|
|
405
|
+
strongEvidence: false,
|
|
406
|
+
...(run.failureKind !== undefined ? { failureKind: run.failureKind } : {}),
|
|
407
|
+
...(run.exitCode !== undefined ? { exitCode: run.exitCode } : {}),
|
|
408
|
+
sessionLog: run.error ? `${run.output}\n${run.error}` : run.output,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
271
411
|
/** Default git runner: every incomplete command result fails closed. */
|
|
272
412
|
export const defaultGitRunner = async (args, cwd, signal, options) => {
|
|
273
413
|
let result;
|
|
@@ -341,10 +481,29 @@ function writePrivatePatchFile(path, patch) {
|
|
|
341
481
|
* Build the `execute` function CycleEngine/runEvolutionCycle consume. Default-off: throws
|
|
342
482
|
* ExecBridgeDisabledError on first call unless enabled.
|
|
343
483
|
*/
|
|
344
|
-
export function makeClaudeExecBridge(opts) {
|
|
484
|
+
export function makeClaudeExecBridge(opts, internal) {
|
|
485
|
+
if (internal?.cursorWorktreeRoot && !opts.agent) {
|
|
486
|
+
throw new Error('custom Cursor worktree root requires an injected agent');
|
|
487
|
+
}
|
|
488
|
+
const cursorWorktreeRoot = internal?.cursorWorktreeRoot ?? joinPath(homedir(), '.cursor', 'worktrees');
|
|
345
489
|
const enabled = opts.enabled ?? (process.env['EVOLVE_EXEC_BRIDGE'] === '1');
|
|
346
|
-
|
|
347
|
-
|
|
490
|
+
if (opts.agentOptions?.workspaceTrust === 'isolated-worktree' && opts.isolation !== 'worktree') {
|
|
491
|
+
throw new WorkspaceTrustRequiresIsolationError();
|
|
492
|
+
}
|
|
493
|
+
const runnerName = opts.runner ?? 'claude';
|
|
494
|
+
const unsupportedBuiltInClaude = runnerName === 'claude' && !opts.agent;
|
|
495
|
+
const unsupportedBuiltInCodex = runnerName === 'codex' && !opts.agent;
|
|
496
|
+
const spec = getRunnerSpec(runnerName); // #66: provider-neutral runner registry
|
|
497
|
+
const resume = opts.resume ? validateAgentSessionResume(opts.resume, spec.name) : undefined;
|
|
498
|
+
// Do not construct uncontained built-in runners. Keep the bridge factory side-effect free so
|
|
499
|
+
// default-OFF callers retain the documented first-call failure semantics.
|
|
500
|
+
const agent = opts.agent ?? (unsupportedBuiltInClaude || unsupportedBuiltInCodex
|
|
501
|
+
? (() => {
|
|
502
|
+
if (unsupportedBuiltInCodex)
|
|
503
|
+
throw new UnsupportedCodexBuiltInRunnerError();
|
|
504
|
+
throw new UnsupportedClaudeBuiltInRunnerError();
|
|
505
|
+
})
|
|
506
|
+
: spec.makeRunner(opts.agentOptions));
|
|
348
507
|
const git = opts.git ?? defaultGitRunner;
|
|
349
508
|
const gitPatchWriter = opts.gitPatchWriter ?? (git === defaultGitRunner ? defaultGitPatchWriter : undefined);
|
|
350
509
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -353,24 +512,39 @@ export function makeClaudeExecBridge(opts) {
|
|
|
353
512
|
// Fail-fast when a run that can write/execute against the tree with no inner sandbox we control is requested
|
|
354
513
|
// without the throwaway worktree as containment (allowedRoots gates the cwd but can't stop a write outside it).
|
|
355
514
|
// Only when WE build the runner — an injected `agent` bypasses the built-in runner:
|
|
356
|
-
// - codex:
|
|
515
|
+
// - codex: fail-closed above because workspace-write does not contain host reads.
|
|
357
516
|
// - cursor: gated UNCONDITIONALLY. cursor-agent base `-p` already has write+shell access, its skipPermissions
|
|
358
517
|
// path is refused by runnerRegistry, and the runner is an unverified scaffold (#66/#181), so we do not let
|
|
359
518
|
// default cursor touch the real tree until run-verified. (Bugbot High #181)
|
|
360
|
-
//
|
|
361
|
-
const needsIsolation = opts.runner === 'cursor' || opts.runner === 'gemini'
|
|
362
|
-
|| (opts.runner === 'codex' && opts.agentOptions?.skipPermissions === true);
|
|
519
|
+
// Built-in Claude and Codex are fail-closed above.
|
|
520
|
+
const needsIsolation = opts.runner === 'cursor' || opts.runner === 'gemini';
|
|
363
521
|
if (!opts.agent && needsIsolation && opts.isolation !== 'worktree') {
|
|
364
522
|
throw new UnsandboxedFullAccessRequiresIsolationError();
|
|
365
523
|
}
|
|
524
|
+
if (opts.runner === 'cursor' && !opts.agent) {
|
|
525
|
+
throw new UnsupportedCursorBuiltInRunnerError();
|
|
526
|
+
}
|
|
366
527
|
return async (mutation, decision) => {
|
|
367
528
|
if (!enabled)
|
|
368
529
|
throw new ExecBridgeDisabledError();
|
|
369
530
|
// Deny-by-default guardrail: an autonomous agent may only edit allowlisted repos. Checked before the
|
|
370
531
|
// agent is ever spawned, so a forbidden cwd never runs anything.
|
|
371
|
-
|
|
532
|
+
const repoCwd = opts.allowedRoots === undefined
|
|
533
|
+
? opts.cwd
|
|
534
|
+
: canonicalPathWithinRoots(opts.cwd, opts.allowedRoots);
|
|
535
|
+
if (!repoCwd) {
|
|
372
536
|
throw new ExecBridgeForbiddenError(opts.cwd);
|
|
373
537
|
}
|
|
538
|
+
if (unsupportedBuiltInClaude)
|
|
539
|
+
throw new UnsupportedClaudeBuiltInRunnerError();
|
|
540
|
+
if (unsupportedBuiltInCodex)
|
|
541
|
+
throw new UnsupportedCodexBuiltInRunnerError();
|
|
542
|
+
const assertRepoCwdStillAllowed = () => {
|
|
543
|
+
if (opts.allowedRoots !== undefined
|
|
544
|
+
&& canonicalPathWithinRoots(repoCwd, opts.allowedRoots) !== repoCwd) {
|
|
545
|
+
throw new ExecBridgeForbiddenError(opts.cwd);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
374
548
|
const resolveId = decision.selectedAssetId ?? decision.selectedGeneId;
|
|
375
549
|
const resolved = resolveId && opts.resolveGene ? await opts.resolveGene(resolveId) : null;
|
|
376
550
|
// Trust gate (finding #39.3): for unattended runs, only embed a gene we trust — an untrusted gene's strategy
|
|
@@ -384,14 +558,18 @@ export function makeClaudeExecBridge(opts) {
|
|
|
384
558
|
// use-case ①: inject the personality style block from the state applySelectForRun just persisted.
|
|
385
559
|
...(opts.personality ? { personality: opts.personality.currentState() } : {}),
|
|
386
560
|
});
|
|
561
|
+
assertRepoCwdStillAllowed();
|
|
387
562
|
// Isolation: atomically reserve an unpredictable private temp container, then let git create the absent
|
|
388
563
|
// worktree path inside it. Cleanup is armed only after git succeeds and the resulting directory is verified.
|
|
389
564
|
const isolate = opts.isolation === 'worktree';
|
|
565
|
+
const managedCursorIsolation = isolate && opts.runner === 'cursor' && resume?.runner === 'cursor';
|
|
390
566
|
if (opts.signal?.aborted)
|
|
391
567
|
return cancelledExecutionResult(undefined);
|
|
392
568
|
const reservation = isolate ? reserveWorktreePath() : undefined;
|
|
393
|
-
|
|
569
|
+
let workDir = reservation?.workDir ?? repoCwd;
|
|
394
570
|
let worktreeIdentity;
|
|
571
|
+
let managedCursorIdentity;
|
|
572
|
+
const managedWorktreeName = managedCursorIsolation ? `evolver-${randomUUID()}` : undefined;
|
|
395
573
|
let result;
|
|
396
574
|
let observedRun;
|
|
397
575
|
let patchRef;
|
|
@@ -401,6 +579,8 @@ export function makeClaudeExecBridge(opts) {
|
|
|
401
579
|
const proofGit = async (args, cwd, checkCancellationAfter = true) => {
|
|
402
580
|
if (opts.signal?.aborted)
|
|
403
581
|
throw new ExecBridgeRunCancelledError();
|
|
582
|
+
if (cwd === repoCwd)
|
|
583
|
+
assertRepoCwdStillAllowed();
|
|
404
584
|
try {
|
|
405
585
|
const output = await git(args, cwd, opts.signal);
|
|
406
586
|
if (checkCancellationAfter && opts.signal?.aborted)
|
|
@@ -417,18 +597,63 @@ export function makeClaudeExecBridge(opts) {
|
|
|
417
597
|
};
|
|
418
598
|
try {
|
|
419
599
|
if (reservation) {
|
|
420
|
-
await proofGit(['worktree', 'add', '--detach', workDir, 'HEAD'],
|
|
600
|
+
await proofGit(['worktree', 'add', '--detach', workDir, 'HEAD'], repoCwd, false);
|
|
421
601
|
worktreeIdentity = verifyWorktreePath(reservation);
|
|
422
602
|
if (opts.signal?.aborted)
|
|
423
603
|
throw new ExecBridgeRunCancelledError();
|
|
424
604
|
}
|
|
605
|
+
if (!reservation)
|
|
606
|
+
assertRepoCwdStillAllowed();
|
|
425
607
|
const run = await agent(prompt, {
|
|
426
608
|
cwd: workDir,
|
|
427
609
|
timeoutMs,
|
|
428
610
|
...(agentEnv ? { env: agentEnv } : {}),
|
|
429
611
|
...(opts.signal ? { signal: opts.signal } : {}),
|
|
612
|
+
...(resume ? { resume } : {}),
|
|
613
|
+
...(managedWorktreeName ? { managedWorktreeName } : {}),
|
|
430
614
|
});
|
|
431
615
|
observedRun = run;
|
|
616
|
+
if (managedWorktreeName) {
|
|
617
|
+
try {
|
|
618
|
+
assertRepoCwdStillAllowed();
|
|
619
|
+
managedCursorIdentity = await resolveManagedCursorWorktree(run.managedWorktreePath, managedWorktreeName, repoCwd, git, cursorWorktreeRoot);
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
if (run.failureKind === 'cancelled' || opts.signal?.aborted)
|
|
623
|
+
throw new ExecBridgeRunCancelledError();
|
|
624
|
+
if (!run.ok)
|
|
625
|
+
throw new AgentRunBeforeManagedWorktreeError();
|
|
626
|
+
if (error instanceof UnsafeWorktreePathError) {
|
|
627
|
+
throw new GitProofError(`Cursor managed worktree verification failed: ${error.message}`);
|
|
628
|
+
}
|
|
629
|
+
throw error;
|
|
630
|
+
}
|
|
631
|
+
if (!managedCursorIdentity) {
|
|
632
|
+
if (run.failureKind === 'cancelled' || opts.signal?.aborted)
|
|
633
|
+
throw new ExecBridgeRunCancelledError();
|
|
634
|
+
if (!run.ok)
|
|
635
|
+
throw new AgentRunBeforeManagedWorktreeError();
|
|
636
|
+
throw new GitProofError('Cursor managed worktree could not be verified');
|
|
637
|
+
}
|
|
638
|
+
workDir = managedCursorIdentity.path;
|
|
639
|
+
}
|
|
640
|
+
// Learning trace (slice 2): one model.called per agent spawn — the headless runner is one opaque
|
|
641
|
+
// model-driven turn from the bridge's viewpoint (per-request fidelity arrives via the proxy's llm_turn
|
|
642
|
+
// records; recordLlmTurn folds those when a caller has them). Best-effort: never affects the result.
|
|
643
|
+
try {
|
|
644
|
+
opts.traceRecorder?.modelCalled({
|
|
645
|
+
...(opts.runner !== undefined ? { provider: opts.runner } : { provider: 'claude' }),
|
|
646
|
+
...(opts.agentOptions?.model !== undefined ? { model: opts.agentOptions.model } : {}),
|
|
647
|
+
...(run.exitCode !== undefined && run.exitCode !== null ? { stopReason: `exit_${run.exitCode}` } : {}),
|
|
648
|
+
});
|
|
649
|
+
if (!run.ok) {
|
|
650
|
+
opts.traceRecorder?.toolFailed({
|
|
651
|
+
toolName: 'agent_runner',
|
|
652
|
+
error: run.error ?? run.failureKind ?? 'agent run failed',
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
catch { /* trace emission is observability only */ }
|
|
432
657
|
if (run.failureKind === 'cancelled' || opts.signal?.aborted)
|
|
433
658
|
throw new ExecBridgeRunCancelledError();
|
|
434
659
|
// A worktree can contain three independent change surfaces after the agent exits: staged tracked changes,
|
|
@@ -456,6 +681,8 @@ export function makeClaudeExecBridge(opts) {
|
|
|
456
681
|
numstat = await proofGit(['diff', '--numstat', 'HEAD'], workDir);
|
|
457
682
|
if (isolate && stat.files > 0) {
|
|
458
683
|
if (gitPatchWriter) {
|
|
684
|
+
if (workDir === repoCwd)
|
|
685
|
+
assertRepoCwdStillAllowed();
|
|
459
686
|
patchRef = joinPath(tmpdir(), `evolver-patch-${randomUUID()}.diff`);
|
|
460
687
|
try {
|
|
461
688
|
await gitPatchWriter(['diff', '--binary', '--full-index', 'HEAD'], workDir, patchRef, opts.signal, () => { ownsPatchRef = true; });
|
|
@@ -567,13 +794,26 @@ export function makeClaudeExecBridge(opts) {
|
|
|
567
794
|
if (error instanceof ExecBridgeRunCancelledError || opts.signal?.aborted) {
|
|
568
795
|
result = cancelledExecutionResult(observedRun);
|
|
569
796
|
}
|
|
797
|
+
else if (error instanceof AgentRunBeforeManagedWorktreeError && observedRun) {
|
|
798
|
+
result = failedAgentRunResult(observedRun);
|
|
799
|
+
}
|
|
570
800
|
else if (error instanceof GitProofError && observedRun) {
|
|
571
801
|
result = failedProofExecutionResult(observedRun, error, failedProof);
|
|
572
802
|
}
|
|
573
803
|
else {
|
|
804
|
+
if (managedCursorIdentity) {
|
|
805
|
+
try {
|
|
806
|
+
assertRepoCwdStillAllowed();
|
|
807
|
+
await cleanupManagedCursorWorktree(managedCursorIdentity, git, repoCwd);
|
|
808
|
+
}
|
|
809
|
+
catch {
|
|
810
|
+
// Preserve the primary execution error; the verified worktree remains diagnosable.
|
|
811
|
+
}
|
|
812
|
+
}
|
|
574
813
|
if (reservation) {
|
|
575
814
|
try {
|
|
576
|
-
|
|
815
|
+
assertRepoCwdStillAllowed();
|
|
816
|
+
await cleanupWorktreeReservation(reservation, worktreeIdentity, git, repoCwd);
|
|
577
817
|
}
|
|
578
818
|
catch {
|
|
579
819
|
// Preserve the primary execution error. The abandoned reservation remains private and diagnosable.
|
|
@@ -590,17 +830,29 @@ export function makeClaudeExecBridge(opts) {
|
|
|
590
830
|
catch { /* best-effort cleanup must not replace the execution result or its primary failure */ }
|
|
591
831
|
}
|
|
592
832
|
}
|
|
833
|
+
let cleanupError;
|
|
834
|
+
if (managedCursorIdentity) {
|
|
835
|
+
try {
|
|
836
|
+
assertRepoCwdStillAllowed();
|
|
837
|
+
await cleanupManagedCursorWorktree(managedCursorIdentity, git, repoCwd);
|
|
838
|
+
}
|
|
839
|
+
catch (error) {
|
|
840
|
+
cleanupError = error;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
593
843
|
if (reservation) {
|
|
594
844
|
try {
|
|
595
|
-
|
|
845
|
+
assertRepoCwdStillAllowed();
|
|
846
|
+
await cleanupWorktreeReservation(reservation, worktreeIdentity, git, repoCwd);
|
|
596
847
|
}
|
|
597
848
|
catch (error) {
|
|
598
|
-
|
|
599
|
-
// primary classification; the private reservation remains available for diagnosis.
|
|
600
|
-
if (result.outcome.status === 'success')
|
|
601
|
-
throw error;
|
|
849
|
+
cleanupError ??= error;
|
|
602
850
|
}
|
|
603
851
|
}
|
|
852
|
+
// A successful run must not hide abandoned edits/resources. Both cleanup paths are attempted first so one
|
|
853
|
+
// failure cannot strand the other worktree. Failed and cancelled results retain their primary classification.
|
|
854
|
+
if (cleanupError && result.outcome.status === 'success')
|
|
855
|
+
throw cleanupError;
|
|
604
856
|
return result;
|
|
605
857
|
};
|
|
606
858
|
}
|
package/dist/exec/prompt.js
CHANGED
|
@@ -5,6 +5,7 @@ import { renderPersonalityBlock } from '../personality/prompt.js';
|
|
|
5
5
|
// trust gate (only embed trusted gene strategies); this just blunts the obvious "ignore your instructions" /
|
|
6
6
|
// fake-role-tag attacks in whatever content does flow through. Kept tight to avoid redacting real strategy text.
|
|
7
7
|
const INJECTION_PATTERNS = [
|
|
8
|
+
/(?<![a-z0-9])(?:ignore|disregard|forget)[_-]+(?:all[_-]+)?(?:previous|above|prior|preceding|earlier)[_-]+(?:instructions?|prompts?|context|rules?|messages?)(?:[_-]+[a-z0-9]+){0,12}/gi,
|
|
8
9
|
/\b(ignore|disregard|forget)\b[^.\n]{0,40}\b(previous|above|prior|preceding|earlier|all)\b[^.\n]{0,30}\b(instruction|prompt|context|rule|message)/gi,
|
|
9
10
|
/\b(new|updated|real|actual)\b[^.\n]{0,20}\b(instruction|system prompt|task|directive)s?\s*:/gi,
|
|
10
11
|
/<\/?\s*(system|user|assistant|instructions?|im_start|im_end)\s*>/gi,
|
|
@@ -42,6 +43,9 @@ function renderAntiWarning(warning, index, sanitize) {
|
|
|
42
43
|
export function renderExecPrompt(input) {
|
|
43
44
|
const { mutation: m, decision: d, gene, validationCmds, personality } = input;
|
|
44
45
|
const s = sanitizeInjection; // every embedded (potentially untrusted) field is sanitized (finding #39.3)
|
|
46
|
+
// Hub capability gaps may steer curriculum selection, but they are control-plane data rather than executable
|
|
47
|
+
// task instructions. Keep their raw values out of the real-agent prompt while retaining ordinary/local signals.
|
|
48
|
+
const promptSignals = m.trigger_signals.filter((signal) => !signal.toLowerCase().startsWith('curriculum_target:gap:'));
|
|
45
49
|
const lines = [
|
|
46
50
|
'You are an autonomous coding agent applying ONE focused, minimal change.',
|
|
47
51
|
'',
|
|
@@ -51,7 +55,7 @@ export function renderExecPrompt(input) {
|
|
|
51
55
|
'## Target',
|
|
52
56
|
`Area/file: ${s(m.target)}`,
|
|
53
57
|
`Category: ${m.category} Risk: ${m.risk_level}`,
|
|
54
|
-
`Triggering signals: ${s(
|
|
58
|
+
`Triggering signals: ${s(promptSignals.join(', ')) || '(none)'}`,
|
|
55
59
|
];
|
|
56
60
|
if (d.selectedGeneId && gene) {
|
|
57
61
|
lines.push('', `## Strategy (learned gene ${d.selectedGeneId})`);
|