@evomap/evolver-core 2.0.0-beta.19 → 2.0.0-beta.22
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/assets/gep/genes.jsonl +5 -5
- package/dist/algo/bans.d.ts +10 -1
- package/dist/algo/bans.js +56 -6
- package/dist/algo/candidateAssembly.d.ts +25 -2
- package/dist/algo/candidateAssembly.js +143 -20
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/cycleEngine.d.ts +40 -5
- package/dist/algo/cycleEngine.js +277 -37
- package/dist/algo/evolutionEvent.d.ts +26 -0
- package/dist/algo/evolutionEvent.js +32 -0
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +34 -1
- package/dist/algo/geneHealth.js +42 -0
- package/dist/algo/geneIntake.d.ts +46 -1
- package/dist/algo/geneIntake.js +121 -10
- package/dist/algo/geneSelection.d.ts +115 -6
- package/dist/algo/geneSelection.js +459 -32
- package/dist/algo/index.d.ts +4 -1
- package/dist/algo/index.js +4 -1
- package/dist/algo/kautoProjection.d.ts +41 -0
- package/dist/algo/kautoProjection.js +95 -0
- package/dist/algo/kautoValidator.d.ts +68 -0
- package/dist/algo/kautoValidator.js +256 -0
- package/dist/algo/orchestrator.d.ts +14 -1
- package/dist/algo/orchestrator.js +9 -2
- package/dist/algo/solidify.d.ts +11 -2
- package/dist/algo/solidify.js +37 -7
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetrepair/hubRejection.d.ts +12 -0
- package/dist/assetrepair/hubRejection.js +109 -0
- package/dist/assetrepair/index.d.ts +2 -0
- package/dist/assetrepair/index.js +2 -0
- package/dist/assetrepair/repair.d.ts +33 -0
- package/dist/assetrepair/repair.js +155 -0
- package/dist/assetstore/assetSidecarRecords.js +5 -1
- package/dist/assetstore/assetStoreHealth.d.ts +2 -3
- package/dist/assetstore/assetStoreHealth.js +4 -10
- package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
- package/dist/assetstore/foreignJsonlSource.js +150 -0
- package/dist/assetstore/index.d.ts +3 -0
- package/dist/assetstore/index.js +3 -0
- package/dist/assetstore/learningHistory.js +3 -3
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +8 -0
- package/dist/assetstore/localJsonl.js +128 -3
- package/dist/assetstore/provenance.d.ts +38 -3
- package/dist/assetstore/provenance.js +103 -45
- package/dist/assetstore/provider.d.ts +4 -0
- package/dist/assetstore/reviewFilter.d.ts +19 -1
- package/dist/assetstore/reviewFilter.js +36 -0
- package/dist/assetstore/unionReadStore.d.ts +25 -0
- package/dist/assetstore/unionReadStore.js +119 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +4 -3
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +4 -3
- package/dist/benchmark/index.d.ts +2 -1
- package/dist/benchmark/index.js +2 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- 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/lifecycleBootstrap.d.ts +111 -0
- package/dist/bootstrap/lifecycleBootstrap.js +433 -0
- package/dist/bootstrap/v1EnvCompat.d.ts +3 -0
- package/dist/bootstrap/v1EnvCompat.js +48 -1
- 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 +2 -1
- package/dist/events/ingest.js +6 -0
- package/dist/exec/autoExec.d.ts +30 -3
- package/dist/exec/autoExec.js +294 -29
- package/dist/exec/autonomousCycle.d.ts +7 -0
- package/dist/exec/autonomousCycle.js +2 -0
- package/dist/exec/claudeBridge.d.ts +28 -6
- package/dist/exec/claudeBridge.js +411 -258
- package/dist/exec/executionBinding.d.ts +414 -0
- package/dist/exec/executionBinding.js +588 -0
- package/dist/exec/index.d.ts +1 -0
- package/dist/exec/index.js +1 -0
- package/dist/exec/proofOfWork.d.ts +1 -1
- package/dist/exec/proofOfWork.js +2 -2
- package/dist/exec/runnerRegistry.d.ts +5 -0
- package/dist/exec/runnerRegistry.js +54 -14
- package/dist/hub/capability.d.ts +40 -1
- package/dist/hub/conversationDistiller.d.ts +19 -0
- package/dist/hub/conversationDistiller.js +115 -37
- package/dist/hub/fake.d.ts +1 -0
- package/dist/hub/fake.js +1 -0
- package/dist/hub/index.d.ts +1 -0
- package/dist/hub/index.js +1 -0
- package/dist/hub/recipeCompose.d.ts +27 -0
- package/dist/hub/recipeCompose.js +90 -0
- package/dist/hub/sanitize.js +119 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mailbox/ipcServer.d.ts +2 -0
- package/dist/mailbox/ipcServer.js +20 -0
- package/dist/mailbox/store.d.ts +52 -2
- package/dist/mailbox/store.js +546 -81
- package/dist/modelCompatibility.d.ts +164 -0
- package/dist/modelCompatibility.js +309 -0
- package/dist/observers/valueDigestObserver.d.ts +9 -0
- package/dist/observers/valueDigestObserver.js +35 -2
- package/dist/ops/cleanup.js +1 -1
- 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/ops/valueOutreach.d.ts +3 -1
- package/dist/ops/valueOutreach.js +5 -1
- package/dist/personality/schema.d.ts +18 -18
- package/dist/schema/evolutionGraph.d.ts +784 -0
- package/dist/schema/evolutionGraph.js +187 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/proofOfWork.d.ts +125 -6
- package/dist/schema/proofOfWork.js +102 -4
- package/dist/shadow/shadowHub.js +1 -0
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +148 -0
- package/dist/signals/index.d.ts +3 -1
- package/dist/signals/index.js +3 -1
- package/dist/signals/scopeVocabulary.d.ts +75 -0
- package/dist/signals/scopeVocabulary.js +91 -0
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +43 -0
- package/dist/trace/learningTrace.d.ts +7 -0
- package/dist/trace/learningTrace.js +22 -0
- package/dist/trace/trajectory.d.ts +8 -0
- package/dist/trace/trajectory.js +14 -2
- package/dist/util/fileLock.d.ts +92 -5
- package/dist/util/fileLock.js +809 -70
- package/dist/util/index.d.ts +2 -1
- package/dist/util/index.js +1 -1
- package/dist/verify/index.d.ts +2 -1
- package/dist/verify/index.js +1 -1
- package/dist/verify/sandboxRunner.d.ts +2 -0
- package/dist/verify/sandboxRunner.js +154 -10
- package/dist/verify/sandboxedValidation.d.ts +7 -2
- package/dist/verify/sandboxedValidation.js +102 -24
- package/dist/verify/validation.d.ts +24 -3
- package/dist/verify/validation.js +141 -24
- package/dist/wire/geneHints.d.ts +88 -16
- package/dist/wire/geneHints.js +124 -15
- package/dist/wire/index.d.ts +8 -3
- package/dist/wire/index.js +2 -2
- package/dist/wire/schemaGate.d.ts +21 -0
- package/dist/wire/schemaGate.js +119 -10
- package/package.json +5 -4
|
@@ -308,6 +308,11 @@ const ALLOW_ENV_KEYS = new Set([
|
|
|
308
308
|
'PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'LANG', 'LANGUAGE', 'TERM', 'TZ', 'PWD', 'HOSTNAME',
|
|
309
309
|
'TMPDIR', 'TMP', 'TEMP', 'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE', 'SSL_CERT_DIR',
|
|
310
310
|
'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'http_proxy', 'https_proxy', 'no_proxy',
|
|
311
|
+
// Node's built-in fetch (undici) does NOT honor HTTP(S)_PROXY unless NODE_USE_ENV_PROXY=1 is set (Node 20+).
|
|
312
|
+
// An agent CLI that uses global fetch (e.g. the gemini CLI on system Node) is otherwise unreachable behind a
|
|
313
|
+
// proxy even though the proxy vars above are allowed. This is a neutral runtime knob, not a secret — pair it
|
|
314
|
+
// with the proxy vars so a proxied agent can actually reach its API. Absent (unset) → no-op.
|
|
315
|
+
'NODE_USE_ENV_PROXY',
|
|
311
316
|
// Windows OS essentials (absent on POSIX → no-op there): a process can't run, resolve PATHEXT shims, or find
|
|
312
317
|
// its own per-user config/auth dir without these. They are OS runtime vars, NOT app secrets — the secret
|
|
313
318
|
// env (NODE_SECRET/AWS_*/GH_TOKEN/vendor keys) is still denied by the whitelist. Their absence is why an
|
|
@@ -362,6 +367,78 @@ class ExecBridgeRunCancelledError extends Error {
|
|
|
362
367
|
this.name = 'ExecBridgeRunCancelledError';
|
|
363
368
|
}
|
|
364
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* 绑定执行不能接受无法收到权威截止时间取消信号的旧验证器或写入器。
|
|
372
|
+
* AbortSignal 本身是协作式的;先拒绝不暴露该信号的回调,避免旧 hook
|
|
373
|
+
* 被静默地当作无界工作运行。
|
|
374
|
+
*/
|
|
375
|
+
export class ExecBridgeCancellationContractError extends Error {
|
|
376
|
+
constructor(hook) {
|
|
377
|
+
super(`bound execution ${hook} must accept AbortSignal so deadline cancellation can be enforced`);
|
|
378
|
+
this.name = 'ExecBridgeCancellationContractError';
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
class ExecBridgeRunTimedOutError extends Error {
|
|
382
|
+
constructor() {
|
|
383
|
+
super('exec bridge run timed out');
|
|
384
|
+
this.name = 'ExecBridgeRunTimedOutError';
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* 将绑定运行时限制应用到完整 bridge 生命周期,而不仅是 agent 子进程。验证和补丁持久化等每个操作都会
|
|
389
|
+
* 收到同一个派生信号。无法接受该信号的回调会在绑定运行启动前被拒绝(见
|
|
390
|
+
* ExecBridgeCancellationContractError),因此下面的 Promise.race 仅作为不遵守协作式取消约定时的最后
|
|
391
|
+
* 响应边界,而不是隐式放行旧 hook。
|
|
392
|
+
*/
|
|
393
|
+
function createExecutionDeadline(parentSignal, maxRuntimeMs) {
|
|
394
|
+
const controller = new AbortController();
|
|
395
|
+
let expired = false;
|
|
396
|
+
let rejectInterrupt = () => { };
|
|
397
|
+
const interrupt = new Promise((_, reject) => { rejectInterrupt = reject; });
|
|
398
|
+
const onParentAbort = () => {
|
|
399
|
+
if (controller.signal.aborted)
|
|
400
|
+
return;
|
|
401
|
+
controller.abort(parentSignal?.reason ?? new ExecBridgeRunCancelledError());
|
|
402
|
+
rejectInterrupt(new ExecBridgeRunCancelledError());
|
|
403
|
+
};
|
|
404
|
+
const timer = maxRuntimeMs === undefined ? undefined : setTimeout(() => {
|
|
405
|
+
expired = true;
|
|
406
|
+
controller.abort(new ExecBridgeRunTimedOutError());
|
|
407
|
+
rejectInterrupt(new ExecBridgeRunTimedOutError());
|
|
408
|
+
}, Math.min(maxRuntimeMs, 2_147_483_647));
|
|
409
|
+
parentSignal?.addEventListener('abort', onParentAbort, { once: true });
|
|
410
|
+
if (parentSignal?.aborted)
|
|
411
|
+
onParentAbort();
|
|
412
|
+
return {
|
|
413
|
+
// 未配置本地运行时定时器时保留调用方显式传入的 parent signal,维持既有取消身份;绑定运行始终使用
|
|
414
|
+
// 带截止时间的派生信号。
|
|
415
|
+
signal: maxRuntimeMs === undefined && parentSignal ? parentSignal : controller.signal,
|
|
416
|
+
expired: () => expired,
|
|
417
|
+
assertActive: () => {
|
|
418
|
+
if (expired)
|
|
419
|
+
throw new ExecBridgeRunTimedOutError();
|
|
420
|
+
if (parentSignal?.aborted)
|
|
421
|
+
throw new ExecBridgeRunCancelledError();
|
|
422
|
+
},
|
|
423
|
+
run: async (operation) => {
|
|
424
|
+
if (expired)
|
|
425
|
+
throw new ExecBridgeRunTimedOutError();
|
|
426
|
+
if (parentSignal?.aborted)
|
|
427
|
+
throw new ExecBridgeRunCancelledError();
|
|
428
|
+
const value = await Promise.race([Promise.resolve().then(operation), interrupt]);
|
|
429
|
+
if (expired)
|
|
430
|
+
throw new ExecBridgeRunTimedOutError();
|
|
431
|
+
if (parentSignal?.aborted)
|
|
432
|
+
throw new ExecBridgeRunCancelledError();
|
|
433
|
+
return value;
|
|
434
|
+
},
|
|
435
|
+
dispose: () => {
|
|
436
|
+
if (timer)
|
|
437
|
+
clearTimeout(timer);
|
|
438
|
+
parentSignal?.removeEventListener('abort', onParentAbort);
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
365
442
|
class AgentRunBeforeManagedWorktreeError extends Error {
|
|
366
443
|
constructor() {
|
|
367
444
|
super('agent run failed before a managed worktree was created');
|
|
@@ -389,6 +466,15 @@ function cancelledExecutionResult(run) {
|
|
|
389
466
|
...(run ? { sessionLog: run.error ? `${run.output}\n${run.error}` : run.output } : {}),
|
|
390
467
|
};
|
|
391
468
|
}
|
|
469
|
+
function timedOutExecutionResult(run) {
|
|
470
|
+
return {
|
|
471
|
+
outcome: { status: 'failed', score: 0.1, reason: 'execution timed out' },
|
|
472
|
+
strongEvidence: false,
|
|
473
|
+
failureKind: 'timeout',
|
|
474
|
+
exitCode: run?.exitCode ?? null,
|
|
475
|
+
...(run ? { sessionLog: run.error ? `${run.output}\n${run.error}` : run.output } : {}),
|
|
476
|
+
};
|
|
477
|
+
}
|
|
392
478
|
function failedProofExecutionResult(run, error, proofOfWork) {
|
|
393
479
|
return {
|
|
394
480
|
outcome: { status: 'failed', score: 0.1, reason: `execution proof failed: ${error.message}` },
|
|
@@ -506,7 +592,15 @@ export function makeClaudeExecBridge(opts, internal) {
|
|
|
506
592
|
: spec.makeRunner(opts.agentOptions));
|
|
507
593
|
const git = opts.git ?? defaultGitRunner;
|
|
508
594
|
const gitPatchWriter = opts.gitPatchWriter ?? (git === defaultGitRunner ? defaultGitPatchWriter : undefined);
|
|
509
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
595
|
+
const timeoutMs = Math.min(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS, opts.executionLimits?.maxRuntimeMs ?? Number.MAX_SAFE_INTEGER);
|
|
596
|
+
// 绑定 patch writer 属于受保护的执行生命周期。没有第四个 AbortSignal 参数的旧三参数 writer 无法
|
|
597
|
+
// 观察我们传入的取消信号,不能让它在截止时间后继续运行。
|
|
598
|
+
if (opts.executionLimits && opts.gitPatchWriter && opts.gitPatchWriter.length < 4) {
|
|
599
|
+
throw new ExecBridgeCancellationContractError('gitPatchWriter');
|
|
600
|
+
}
|
|
601
|
+
if (opts.executionLimits && opts.validate && opts.validate.length < 4) {
|
|
602
|
+
throw new ExecBridgeCancellationContractError('validate');
|
|
603
|
+
}
|
|
510
604
|
// secure by default; only THIS runner's auth env reaches it (claude never gets OPENAI_, codex never ANTHROPIC_, #66)
|
|
511
605
|
const agentEnv = opts.scrubEnv === false ? undefined : scrubAgentEnv(process.env, { allowPrefixes: spec.envAllow.prefixes, ...(spec.envAllow.keys ? { allowKeys: spec.envAllow.keys } : {}) });
|
|
512
606
|
// Fail-fast when a run that can write/execute against the tree with no inner sandbox we control is requested
|
|
@@ -559,300 +653,359 @@ export function makeClaudeExecBridge(opts, internal) {
|
|
|
559
653
|
...(opts.personality ? { personality: opts.personality.currentState() } : {}),
|
|
560
654
|
});
|
|
561
655
|
assertRepoCwdStillAllowed();
|
|
562
|
-
|
|
563
|
-
// worktree path inside it. Cleanup is armed only after git succeeds and the resulting directory is verified.
|
|
564
|
-
const isolate = opts.isolation === 'worktree';
|
|
565
|
-
const managedCursorIsolation = isolate && opts.runner === 'cursor' && resume?.runner === 'cursor';
|
|
566
|
-
if (opts.signal?.aborted)
|
|
567
|
-
return cancelledExecutionResult(undefined);
|
|
568
|
-
const reservation = isolate ? reserveWorktreePath() : undefined;
|
|
569
|
-
let workDir = reservation?.workDir ?? repoCwd;
|
|
570
|
-
let worktreeIdentity;
|
|
571
|
-
let managedCursorIdentity;
|
|
572
|
-
const managedWorktreeName = managedCursorIsolation ? `evolver-${randomUUID()}` : undefined;
|
|
573
|
-
let result;
|
|
574
|
-
let observedRun;
|
|
575
|
-
let patchRef;
|
|
576
|
-
let ownsPatchRef = false;
|
|
577
|
-
let preservePatchRef = false;
|
|
578
|
-
let failedProof;
|
|
579
|
-
const proofGit = async (args, cwd, checkCancellationAfter = true) => {
|
|
580
|
-
if (opts.signal?.aborted)
|
|
581
|
-
throw new ExecBridgeRunCancelledError();
|
|
582
|
-
if (cwd === repoCwd)
|
|
583
|
-
assertRepoCwdStillAllowed();
|
|
584
|
-
try {
|
|
585
|
-
const output = await git(args, cwd, opts.signal);
|
|
586
|
-
if (checkCancellationAfter && opts.signal?.aborted)
|
|
587
|
-
throw new ExecBridgeRunCancelledError();
|
|
588
|
-
return output;
|
|
589
|
-
}
|
|
590
|
-
catch (error) {
|
|
591
|
-
if (error instanceof ExecBridgeRunCancelledError || opts.signal?.aborted)
|
|
592
|
-
throw error;
|
|
593
|
-
if (error instanceof GitProofError)
|
|
594
|
-
throw error;
|
|
595
|
-
throw new GitProofError('git state proof failed');
|
|
596
|
-
}
|
|
597
|
-
};
|
|
656
|
+
const executionDeadline = createExecutionDeadline(opts.signal, opts.executionLimits?.maxRuntimeMs);
|
|
598
657
|
try {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
658
|
+
// Isolation: atomically reserve an unpredictable private temp container, then let git create the absent
|
|
659
|
+
// worktree path inside it. Cleanup is armed only after git succeeds and the resulting directory is verified.
|
|
660
|
+
const isolate = opts.isolation === 'worktree';
|
|
661
|
+
const managedCursorIsolation = isolate && opts.runner === 'cursor' && resume?.runner === 'cursor';
|
|
662
|
+
if (executionDeadline.expired())
|
|
663
|
+
return timedOutExecutionResult(undefined);
|
|
664
|
+
if (opts.signal?.aborted)
|
|
665
|
+
return cancelledExecutionResult(undefined);
|
|
666
|
+
const reservation = isolate ? reserveWorktreePath() : undefined;
|
|
667
|
+
let workDir = reservation?.workDir ?? repoCwd;
|
|
668
|
+
let worktreeIdentity;
|
|
669
|
+
let managedCursorIdentity;
|
|
670
|
+
const managedWorktreeName = managedCursorIsolation ? `evolver-${randomUUID()}` : undefined;
|
|
671
|
+
let result;
|
|
672
|
+
let observedRun;
|
|
673
|
+
let patchRef;
|
|
674
|
+
let ownsPatchRef = false;
|
|
675
|
+
let preservePatchRef = false;
|
|
676
|
+
let failedProof;
|
|
677
|
+
const proofGit = async (args, cwd, checkCancellationAfter = true) => {
|
|
678
|
+
executionDeadline.assertActive();
|
|
679
|
+
if (cwd === repoCwd)
|
|
618
680
|
assertRepoCwdStillAllowed();
|
|
619
|
-
|
|
681
|
+
try {
|
|
682
|
+
const output = await executionDeadline.run(() => git(args, cwd, executionDeadline.signal));
|
|
683
|
+
if (checkCancellationAfter)
|
|
684
|
+
executionDeadline.assertActive();
|
|
685
|
+
return output;
|
|
620
686
|
}
|
|
621
687
|
catch (error) {
|
|
622
|
-
if (
|
|
623
|
-
throw
|
|
624
|
-
if (
|
|
625
|
-
throw
|
|
626
|
-
|
|
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');
|
|
688
|
+
if (error instanceof ExecBridgeRunCancelledError || error instanceof ExecBridgeRunTimedOutError || opts.signal?.aborted || executionDeadline.expired())
|
|
689
|
+
throw error;
|
|
690
|
+
if (error instanceof GitProofError)
|
|
691
|
+
throw error;
|
|
692
|
+
throw new GitProofError('git state proof failed');
|
|
637
693
|
}
|
|
638
|
-
|
|
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.
|
|
694
|
+
};
|
|
643
695
|
try {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (!run.ok) {
|
|
650
|
-
opts.traceRecorder?.toolFailed({
|
|
651
|
-
toolName: 'agent_runner',
|
|
652
|
-
error: run.error ?? run.failureKind ?? 'agent run failed',
|
|
653
|
-
});
|
|
696
|
+
if (reservation) {
|
|
697
|
+
await proofGit(['worktree', 'add', '--detach', workDir, 'HEAD'], repoCwd, false);
|
|
698
|
+
worktreeIdentity = verifyWorktreePath(reservation);
|
|
699
|
+
if (opts.signal?.aborted)
|
|
700
|
+
throw new ExecBridgeRunCancelledError();
|
|
654
701
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
702
|
+
if (!reservation)
|
|
703
|
+
assertRepoCwdStillAllowed();
|
|
704
|
+
const run = await executionDeadline.run(() => agent(prompt, {
|
|
705
|
+
cwd: workDir,
|
|
706
|
+
timeoutMs,
|
|
707
|
+
...(agentEnv ? { env: agentEnv } : {}),
|
|
708
|
+
...(executionDeadline.signal ? { signal: executionDeadline.signal } : {}),
|
|
709
|
+
...(resume ? { resume } : {}),
|
|
710
|
+
...(managedWorktreeName ? { managedWorktreeName } : {}),
|
|
711
|
+
}));
|
|
712
|
+
observedRun = run;
|
|
713
|
+
await executionDeadline.run(() => opts.executionObserver?.onToolDecision?.({
|
|
714
|
+
tool_name: 'agent_runner',
|
|
715
|
+
decision: 'allowed',
|
|
716
|
+
status: run.ok ? 'completed' : 'failed',
|
|
717
|
+
}, executionDeadline.signal));
|
|
718
|
+
if (managedWorktreeName) {
|
|
719
|
+
try {
|
|
720
|
+
assertRepoCwdStillAllowed();
|
|
721
|
+
managedCursorIdentity = await executionDeadline.run(() => resolveManagedCursorWorktree(run.managedWorktreePath, managedWorktreeName, repoCwd, git, cursorWorktreeRoot));
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
if (error instanceof ExecBridgeRunTimedOutError || executionDeadline.expired())
|
|
725
|
+
throw new ExecBridgeRunTimedOutError();
|
|
726
|
+
if (run.failureKind === 'cancelled' || opts.signal?.aborted)
|
|
727
|
+
throw new ExecBridgeRunCancelledError();
|
|
728
|
+
if (!run.ok)
|
|
729
|
+
throw new AgentRunBeforeManagedWorktreeError();
|
|
730
|
+
if (error instanceof UnsafeWorktreePathError) {
|
|
731
|
+
throw new GitProofError(`Cursor managed worktree verification failed: ${error.message}`);
|
|
732
|
+
}
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
if (!managedCursorIdentity) {
|
|
736
|
+
if (executionDeadline.expired())
|
|
737
|
+
throw new ExecBridgeRunTimedOutError();
|
|
738
|
+
if (run.failureKind === 'cancelled' || opts.signal?.aborted)
|
|
739
|
+
throw new ExecBridgeRunCancelledError();
|
|
740
|
+
if (!run.ok)
|
|
741
|
+
throw new AgentRunBeforeManagedWorktreeError();
|
|
742
|
+
throw new GitProofError('Cursor managed worktree could not be verified');
|
|
743
|
+
}
|
|
744
|
+
workDir = managedCursorIdentity.path;
|
|
676
745
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
746
|
+
// Learning trace (slice 2): one model.called per agent spawn — the headless runner is one opaque
|
|
747
|
+
// model-driven turn from the bridge's viewpoint (per-request fidelity arrives via the proxy's llm_turn
|
|
748
|
+
// records; recordLlmTurn folds those when a caller has them). Best-effort: never affects the result.
|
|
749
|
+
try {
|
|
750
|
+
opts.traceRecorder?.modelCalled({
|
|
751
|
+
...(opts.runner !== undefined ? { provider: opts.runner } : { provider: 'claude' }),
|
|
752
|
+
...(opts.agentOptions?.model !== undefined ? { model: opts.agentOptions.model } : {}),
|
|
753
|
+
...(run.exitCode !== undefined && run.exitCode !== null ? { stopReason: `exit_${run.exitCode}` } : {}),
|
|
754
|
+
});
|
|
755
|
+
// Prefer an authoritative native session id from the runner when present. This covers Gemini and
|
|
756
|
+
// any future harness that reports a session id without also writing proxy llm_turn rows.
|
|
757
|
+
if (typeof run.sessionId === 'string' && run.sessionId.length > 0) {
|
|
687
758
|
try {
|
|
688
|
-
|
|
689
|
-
// A successful writer owns its result even when an older injected implementation ignores the
|
|
690
|
-
// optional callback. On rejection, only the callback can prove that a partial file is ours.
|
|
691
|
-
ownsPatchRef = true;
|
|
759
|
+
opts.traceRecorder?.bindSessionId(run.sessionId);
|
|
692
760
|
}
|
|
693
|
-
catch
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
761
|
+
catch { /* observability only */ }
|
|
762
|
+
}
|
|
763
|
+
if (!run.ok) {
|
|
764
|
+
opts.traceRecorder?.toolFailed({
|
|
765
|
+
toolName: 'agent_runner',
|
|
766
|
+
error: run.error ?? run.failureKind ?? 'agent run failed',
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
catch { /* trace emission is observability only */ }
|
|
771
|
+
executionDeadline.assertActive();
|
|
772
|
+
// A worktree can contain three independent change surfaces after the agent exits: staged tracked changes,
|
|
773
|
+
// unstaged tracked changes, and untracked files. `git diff` alone sees only the second. In an isolated
|
|
774
|
+
// worktree it is safe to mark untracked files intent-to-add temporarily, which makes one `git diff HEAD`
|
|
775
|
+
// snapshot cover all three without staging their contents or disturbing the agent's existing staged state.
|
|
776
|
+
// Reset only those temporary index entries before validation so hooks observe the state the agent left.
|
|
777
|
+
const untrackedFiles = isolate
|
|
778
|
+
? (await proofGit(['ls-files', '--others', '--exclude-standard', '-z'], workDir)).split('\0').filter(Boolean)
|
|
779
|
+
: [];
|
|
780
|
+
if (untrackedFiles.length > 0)
|
|
781
|
+
await proofGit(['add', '--intent-to-add', '--', ...untrackedFiles], workDir);
|
|
782
|
+
let stat;
|
|
783
|
+
let changedFiles;
|
|
784
|
+
let numstat;
|
|
785
|
+
let patch = '';
|
|
786
|
+
const resetIntentToAdd = async () => {
|
|
787
|
+
if (untrackedFiles.length > 0) {
|
|
788
|
+
await executionDeadline.run(() => git(['reset', '--quiet', '--', ...untrackedFiles], workDir, executionDeadline.signal));
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
try {
|
|
792
|
+
stat = parseGitShortstat(await proofGit(['diff', '--shortstat', 'HEAD'], workDir));
|
|
793
|
+
changedFiles = (await proofGit(['diff', '--name-only', 'HEAD'], workDir)).split('\n').map((s) => s.trim()).filter(Boolean);
|
|
794
|
+
numstat = await proofGit(['diff', '--numstat', 'HEAD'], workDir);
|
|
795
|
+
if (isolate && stat.files > 0) {
|
|
796
|
+
if (gitPatchWriter) {
|
|
797
|
+
if (workDir === repoCwd)
|
|
798
|
+
assertRepoCwdStillAllowed();
|
|
799
|
+
const patchDestination = joinPath(tmpdir(), `evolver-patch-${randomUUID()}.diff`);
|
|
800
|
+
patchRef = patchDestination;
|
|
801
|
+
try {
|
|
802
|
+
await executionDeadline.run(() => gitPatchWriter(['diff', '--binary', '--full-index', 'HEAD'], workDir, patchDestination, executionDeadline.signal, () => { ownsPatchRef = true; }));
|
|
803
|
+
// A successful writer owns its result even when an older injected implementation ignores the
|
|
804
|
+
// optional callback. On rejection, only the callback can prove that a partial file is ours.
|
|
805
|
+
ownsPatchRef = true;
|
|
701
806
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
807
|
+
catch (error) {
|
|
808
|
+
if (error instanceof ExecBridgeRunCancelledError || error instanceof ExecBridgeRunTimedOutError || opts.signal?.aborted || executionDeadline.expired())
|
|
809
|
+
throw error;
|
|
810
|
+
if (error instanceof SpawnCaptureFinalizeError) {
|
|
811
|
+
if (error.result.termination === 'cancelled')
|
|
812
|
+
throw new ExecBridgeRunCancelledError();
|
|
813
|
+
if (error.result.termination === 'timeout')
|
|
814
|
+
throw new GitProofError('git patch capture timed out');
|
|
815
|
+
}
|
|
816
|
+
if (error instanceof GitProofError)
|
|
817
|
+
throw error;
|
|
818
|
+
throw new GitProofError('git patch capture failed');
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
patch = await proofGit(['diff', '--binary', '--full-index', 'HEAD'], workDir);
|
|
705
823
|
}
|
|
706
824
|
}
|
|
707
|
-
|
|
708
|
-
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
try {
|
|
828
|
+
await resetIntentToAdd();
|
|
829
|
+
}
|
|
830
|
+
catch (cleanupError) {
|
|
831
|
+
if (cleanupError instanceof ExecBridgeRunCancelledError || opts.signal?.aborted) {
|
|
832
|
+
throw new ExecBridgeRunCancelledError();
|
|
833
|
+
}
|
|
834
|
+
if (cleanupError instanceof ExecBridgeRunTimedOutError || executionDeadline.expired()) {
|
|
835
|
+
throw new ExecBridgeRunTimedOutError();
|
|
836
|
+
}
|
|
837
|
+
// Preserve the primary proof failure for non-cancellation cleanup errors.
|
|
709
838
|
}
|
|
839
|
+
throw error;
|
|
710
840
|
}
|
|
711
|
-
}
|
|
712
|
-
catch (error) {
|
|
713
841
|
try {
|
|
714
842
|
await resetIntentToAdd();
|
|
715
843
|
}
|
|
716
|
-
catch (
|
|
717
|
-
if (
|
|
718
|
-
throw
|
|
844
|
+
catch (error) {
|
|
845
|
+
if (error instanceof ExecBridgeRunCancelledError || error instanceof ExecBridgeRunTimedOutError || opts.signal?.aborted || executionDeadline.expired())
|
|
846
|
+
throw error;
|
|
847
|
+
if (patchRef && ownsPatchRef) {
|
|
848
|
+
failedProof = gitDiffProof(stat, patchRef);
|
|
849
|
+
preservePatchRef = true;
|
|
719
850
|
}
|
|
720
|
-
|
|
851
|
+
throw new GitProofError('git index cleanup failed');
|
|
721
852
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
853
|
+
executionDeadline.assertActive();
|
|
854
|
+
// ENFORCE policy against the ACTUAL diff (finding: prompt.ts only ADVISES the agent "touch at most N
|
|
855
|
+
// file(s) / never modify X"; this is the hard gate). checkPolicy ALWAYS runs the global guards — the
|
|
856
|
+
// system blast hard cap (EVOLVER_HARD_CAP_FILES/LINES), the critical-protected paths (.env, MEMORY.md,
|
|
857
|
+
// package.json, the evolver skill, …), and destructive deletes of those paths — so a no-gene / no-
|
|
858
|
+
// constraints run is no longer un-guarded. The gene's max_files/max_lines/forbidden_paths layer on top.
|
|
859
|
+
// Any violation fails the cycle no matter what the agent did — even when validation would pass.
|
|
860
|
+
const bindingConstraints = opts.executionLimits
|
|
861
|
+
? { max_files: opts.executionLimits.maxFiles, max_lines: opts.executionLimits.maxLines }
|
|
862
|
+
: undefined;
|
|
863
|
+
const constraints = gene?.constraints && bindingConstraints
|
|
864
|
+
? {
|
|
865
|
+
max_files: Math.min(gene.constraints.max_files ?? Number.MAX_SAFE_INTEGER, bindingConstraints.max_files ?? Number.MAX_SAFE_INTEGER),
|
|
866
|
+
max_lines: Math.min(gene.constraints.max_lines ?? Number.MAX_SAFE_INTEGER, bindingConstraints.max_lines ?? Number.MAX_SAFE_INTEGER),
|
|
867
|
+
...(gene.constraints.forbidden_paths ? { forbidden_paths: gene.constraints.forbidden_paths } : {}),
|
|
868
|
+
}
|
|
869
|
+
: gene?.constraints ?? bindingConstraints;
|
|
870
|
+
const violations = checkPolicy({ stat, changedFiles, numstat, ...(constraints ? { constraints } : {}) });
|
|
871
|
+
await executionDeadline.run(() => opts.executionObserver?.onPolicyDecision?.({ version: 'policy.v1', allowed: violations.length === 0, violations }, executionDeadline.signal));
|
|
872
|
+
if (isolate && stat.files > 0 && !patchRef) {
|
|
873
|
+
// preserve the isolated edits as a patch (the worktree itself is removed); the real repo is untouched
|
|
874
|
+
const destination = joinPath(tmpdir(), `evolver-patch-${randomUUID()}.diff`);
|
|
875
|
+
try {
|
|
876
|
+
(opts.writePatchFile ?? writePrivatePatchFile)(destination, patch);
|
|
877
|
+
patchRef = destination;
|
|
878
|
+
ownsPatchRef = true;
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
if (error instanceof GitProofError)
|
|
882
|
+
throw error;
|
|
883
|
+
throw new GitProofError('git patch persistence failed');
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const proof = gitDiffProof(stat, patchRef);
|
|
887
|
+
if (proof.git_diff?.patch_ref) {
|
|
888
|
+
const patchReference = proof.git_diff.patch_ref;
|
|
889
|
+
await executionDeadline.run(() => opts.executionObserver?.onProofReference?.({ kind: proof.kind, ref: patchReference }, executionDeadline.signal));
|
|
890
|
+
}
|
|
891
|
+
// Success: prefer the authoritative validation hook; otherwise "agent succeeded AND produced a diff".
|
|
892
|
+
let passed = run.ok && stat.files > 0;
|
|
893
|
+
let score = passed ? 0.7 : run.ok ? 0.4 : 0.1; // ran-but-no-change is weak, not a clean failure
|
|
894
|
+
// Only validate a change that already respects the constraints — a constraint-violating diff is never
|
|
895
|
+
// a success regardless of what its tests say.
|
|
896
|
+
if (run.ok && stat.files > 0 && opts.validate && violations.length === 0) {
|
|
897
|
+
executionDeadline.assertActive();
|
|
898
|
+
const v = await executionDeadline.run(() => opts.validate(mutation, decision, workDir, executionDeadline.signal));
|
|
899
|
+
if (v.validator)
|
|
900
|
+
await executionDeadline.run(() => opts.executionObserver?.onValidatorDecision?.(v.validator, executionDeadline.signal));
|
|
901
|
+
else
|
|
902
|
+
await executionDeadline.run(() => opts.executionObserver?.onValidatorDecision?.({
|
|
903
|
+
id: 'validation', version: 'validation.v1', status: 'ran', passed: v.passed, score: v.score,
|
|
904
|
+
results: [], skipped: [], isolated: false,
|
|
905
|
+
}, executionDeadline.signal));
|
|
906
|
+
passed = v.passed;
|
|
907
|
+
score = v.score ?? (v.passed ? 0.9 : 0.2);
|
|
908
|
+
}
|
|
909
|
+
else if (opts.validate && violations.length > 0) {
|
|
910
|
+
await executionDeadline.run(() => opts.executionObserver?.onValidatorDecision?.({
|
|
911
|
+
id: 'validation', version: 'validation.v1', status: 'not_run', reason: 'policy_denied',
|
|
912
|
+
results: [], skipped: [], isolated: false,
|
|
913
|
+
}, executionDeadline.signal));
|
|
914
|
+
}
|
|
915
|
+
let reason;
|
|
916
|
+
if (violations.length > 0) {
|
|
917
|
+
passed = false;
|
|
918
|
+
score = Math.min(score, 0.1);
|
|
919
|
+
reason = summarizeViolations(violations);
|
|
920
|
+
}
|
|
921
|
+
preservePatchRef = patchRef !== undefined && ownsPatchRef;
|
|
922
|
+
const failureIdentity = !passed && stat.files > 0
|
|
923
|
+
? { rootAttemptId: mutation.id, executionId: randomUUID() }
|
|
924
|
+
: undefined;
|
|
925
|
+
result = {
|
|
926
|
+
outcome: { status: passed ? 'success' : 'failed', score, ...(reason ? { reason } : {}) },
|
|
927
|
+
proofOfWork: proof,
|
|
928
|
+
strongEvidence: passed && stat.files > 0,
|
|
929
|
+
...(failureIdentity ? { failureIdentity } : {}),
|
|
930
|
+
...(run.failureKind !== undefined ? { failureKind: run.failureKind } : {}),
|
|
931
|
+
...(run.exitCode !== undefined ? { exitCode: run.exitCode } : {}),
|
|
932
|
+
...(run.sessionId ? { nativeSessionId: run.sessionId } : {}),
|
|
933
|
+
// On a FAILED outcome, hand the agent transcript (stdout + stderr) to the cycle engine as host-side
|
|
934
|
+
// triage context (#279): an empty transcript -> host_no_transcript, a provider-error string ->
|
|
935
|
+
// host_provider_error. Omitted on success (failure-only context; never persisted).
|
|
936
|
+
...(passed ? {} : { sessionLog: run.error ? `${run.output}\n${run.error}` : run.output }),
|
|
937
|
+
};
|
|
726
938
|
}
|
|
727
939
|
catch (error) {
|
|
728
|
-
if (error instanceof
|
|
729
|
-
|
|
730
|
-
if (patchRef && ownsPatchRef) {
|
|
731
|
-
failedProof = gitDiffProof(stat, patchRef);
|
|
732
|
-
preservePatchRef = true;
|
|
940
|
+
if (error instanceof ExecBridgeRunTimedOutError || executionDeadline.expired()) {
|
|
941
|
+
result = timedOutExecutionResult(observedRun);
|
|
733
942
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
if (opts.signal?.aborted)
|
|
737
|
-
throw new ExecBridgeRunCancelledError();
|
|
738
|
-
// ENFORCE policy against the ACTUAL diff (finding: prompt.ts only ADVISES the agent "touch at most N
|
|
739
|
-
// file(s) / never modify X"; this is the hard gate). checkPolicy ALWAYS runs the global guards — the
|
|
740
|
-
// system blast hard cap (EVOLVER_HARD_CAP_FILES/LINES), the critical-protected paths (.env, MEMORY.md,
|
|
741
|
-
// package.json, the evolver skill, …), and destructive deletes of those paths — so a no-gene / no-
|
|
742
|
-
// constraints run is no longer un-guarded. The gene's max_files/max_lines/forbidden_paths layer on top.
|
|
743
|
-
// Any violation fails the cycle no matter what the agent did — even when validation would pass.
|
|
744
|
-
const violations = checkPolicy({ stat, changedFiles, numstat, ...(gene?.constraints ? { constraints: gene.constraints } : {}) });
|
|
745
|
-
if (isolate && stat.files > 0 && !patchRef) {
|
|
746
|
-
// preserve the isolated edits as a patch (the worktree itself is removed); the real repo is untouched
|
|
747
|
-
const destination = joinPath(tmpdir(), `evolver-patch-${randomUUID()}.diff`);
|
|
748
|
-
try {
|
|
749
|
-
(opts.writePatchFile ?? writePrivatePatchFile)(destination, patch);
|
|
750
|
-
patchRef = destination;
|
|
751
|
-
ownsPatchRef = true;
|
|
943
|
+
else if (error instanceof ExecBridgeRunCancelledError || opts.signal?.aborted) {
|
|
944
|
+
result = cancelledExecutionResult(observedRun);
|
|
752
945
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
throw error;
|
|
756
|
-
throw new GitProofError('git patch persistence failed');
|
|
946
|
+
else if (error instanceof AgentRunBeforeManagedWorktreeError && observedRun) {
|
|
947
|
+
result = failedAgentRunResult(observedRun);
|
|
757
948
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
throw new ExecBridgeRunCancelledError();
|
|
771
|
-
passed = v.passed;
|
|
772
|
-
score = v.score ?? (v.passed ? 0.9 : 0.2);
|
|
773
|
-
}
|
|
774
|
-
let reason;
|
|
775
|
-
if (violations.length > 0) {
|
|
776
|
-
passed = false;
|
|
777
|
-
score = Math.min(score, 0.1);
|
|
778
|
-
reason = summarizeViolations(violations);
|
|
779
|
-
}
|
|
780
|
-
preservePatchRef = patchRef !== undefined && ownsPatchRef;
|
|
781
|
-
result = {
|
|
782
|
-
outcome: { status: passed ? 'success' : 'failed', score, ...(reason ? { reason } : {}) },
|
|
783
|
-
proofOfWork: proof,
|
|
784
|
-
strongEvidence: passed && stat.files > 0,
|
|
785
|
-
...(run.failureKind !== undefined ? { failureKind: run.failureKind } : {}),
|
|
786
|
-
...(run.exitCode !== undefined ? { exitCode: run.exitCode } : {}),
|
|
787
|
-
// On a FAILED outcome, hand the agent transcript (stdout + stderr) to the cycle engine as host-side
|
|
788
|
-
// triage context (#279): an empty transcript -> host_no_transcript, a provider-error string ->
|
|
789
|
-
// host_provider_error. Omitted on success (failure-only context; never persisted).
|
|
790
|
-
...(passed ? {} : { sessionLog: run.error ? `${run.output}\n${run.error}` : run.output }),
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
catch (error) {
|
|
794
|
-
if (error instanceof ExecBridgeRunCancelledError || opts.signal?.aborted) {
|
|
795
|
-
result = cancelledExecutionResult(observedRun);
|
|
796
|
-
}
|
|
797
|
-
else if (error instanceof AgentRunBeforeManagedWorktreeError && observedRun) {
|
|
798
|
-
result = failedAgentRunResult(observedRun);
|
|
799
|
-
}
|
|
800
|
-
else if (error instanceof GitProofError && observedRun) {
|
|
801
|
-
result = failedProofExecutionResult(observedRun, error, failedProof);
|
|
802
|
-
}
|
|
803
|
-
else {
|
|
804
|
-
if (managedCursorIdentity) {
|
|
805
|
-
try {
|
|
806
|
-
assertRepoCwdStillAllowed();
|
|
807
|
-
await cleanupManagedCursorWorktree(managedCursorIdentity, git, repoCwd);
|
|
949
|
+
else if (error instanceof GitProofError && observedRun) {
|
|
950
|
+
result = failedProofExecutionResult(observedRun, error, failedProof);
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
if (managedCursorIdentity) {
|
|
954
|
+
try {
|
|
955
|
+
assertRepoCwdStillAllowed();
|
|
956
|
+
await cleanupManagedCursorWorktree(managedCursorIdentity, git, repoCwd);
|
|
957
|
+
}
|
|
958
|
+
catch {
|
|
959
|
+
// Preserve the primary execution error; the verified worktree remains diagnosable.
|
|
960
|
+
}
|
|
808
961
|
}
|
|
809
|
-
|
|
810
|
-
|
|
962
|
+
if (reservation) {
|
|
963
|
+
try {
|
|
964
|
+
assertRepoCwdStillAllowed();
|
|
965
|
+
await cleanupWorktreeReservation(reservation, worktreeIdentity, git, repoCwd);
|
|
966
|
+
}
|
|
967
|
+
catch {
|
|
968
|
+
// Preserve the primary execution error. The abandoned reservation remains private and diagnosable.
|
|
969
|
+
}
|
|
811
970
|
}
|
|
971
|
+
throw error;
|
|
812
972
|
}
|
|
813
|
-
|
|
973
|
+
}
|
|
974
|
+
finally {
|
|
975
|
+
if (!preservePatchRef && ownsPatchRef && patchRef) {
|
|
814
976
|
try {
|
|
815
|
-
|
|
816
|
-
await cleanupWorktreeReservation(reservation, worktreeIdentity, git, repoCwd);
|
|
817
|
-
}
|
|
818
|
-
catch {
|
|
819
|
-
// Preserve the primary execution error. The abandoned reservation remains private and diagnosable.
|
|
977
|
+
(opts.removePatchFile ?? ((path) => rmSync(path, { force: true })))(patchRef);
|
|
820
978
|
}
|
|
979
|
+
catch { /* best-effort cleanup must not replace the execution result or its primary failure */ }
|
|
821
980
|
}
|
|
822
|
-
throw error;
|
|
823
981
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
if (!preservePatchRef && ownsPatchRef && patchRef) {
|
|
982
|
+
let cleanupError;
|
|
983
|
+
if (managedCursorIdentity) {
|
|
827
984
|
try {
|
|
828
|
-
(
|
|
985
|
+
assertRepoCwdStillAllowed();
|
|
986
|
+
await cleanupManagedCursorWorktree(managedCursorIdentity, git, repoCwd);
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
cleanupError = error;
|
|
829
990
|
}
|
|
830
|
-
catch { /* best-effort cleanup must not replace the execution result or its primary failure */ }
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
let cleanupError;
|
|
834
|
-
if (managedCursorIdentity) {
|
|
835
|
-
try {
|
|
836
|
-
assertRepoCwdStillAllowed();
|
|
837
|
-
await cleanupManagedCursorWorktree(managedCursorIdentity, git, repoCwd);
|
|
838
991
|
}
|
|
839
|
-
|
|
840
|
-
|
|
992
|
+
if (reservation) {
|
|
993
|
+
try {
|
|
994
|
+
assertRepoCwdStillAllowed();
|
|
995
|
+
await cleanupWorktreeReservation(reservation, worktreeIdentity, git, repoCwd);
|
|
996
|
+
}
|
|
997
|
+
catch (error) {
|
|
998
|
+
cleanupError ??= error;
|
|
999
|
+
}
|
|
841
1000
|
}
|
|
1001
|
+
// A successful run must not hide abandoned edits/resources. Both cleanup paths are attempted first so one
|
|
1002
|
+
// failure cannot strand the other worktree. Failed and cancelled results retain their primary classification.
|
|
1003
|
+
if (cleanupError && result.outcome.status === 'success')
|
|
1004
|
+
throw cleanupError;
|
|
1005
|
+
return result;
|
|
842
1006
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
assertRepoCwdStillAllowed();
|
|
846
|
-
await cleanupWorktreeReservation(reservation, worktreeIdentity, git, repoCwd);
|
|
847
|
-
}
|
|
848
|
-
catch (error) {
|
|
849
|
-
cleanupError ??= error;
|
|
850
|
-
}
|
|
1007
|
+
finally {
|
|
1008
|
+
executionDeadline.dispose();
|
|
851
1009
|
}
|
|
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;
|
|
856
|
-
return result;
|
|
857
1010
|
};
|
|
858
1011
|
}
|