@miraland-labs/conduit-bridge 0.16.188 → 0.16.190
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/cli.js +25 -6
- package/dist/execution.js +74 -18
- package/dist/git-witness.js +38 -0
- package/dist/preflight.js +66 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { BOOTSTRAP_RESULTS, buildSovereignExecutionFacts, workspaceIsClean } fro
|
|
|
20
20
|
import { ensureCheckout } from "./checkout.js";
|
|
21
21
|
import { buildOnShiftIntentProof, managedWorkspacePath, maybeApplyOnShiftIntent } from "./on-shift-apply.js";
|
|
22
22
|
import { expandOpsValue, loadOpsEnv, OPS_VERBS, runOps } from "./ops.js";
|
|
23
|
-
import { pumpExecutionSlots, renewLeases } from "./execution.js";
|
|
23
|
+
import { assignmentsPending, pumpExecutionSlots, renewLeases } from "./execution.js";
|
|
24
24
|
import { createRehearsalWorktree, rehearsalWorktreePath, removeAttemptWorktree } from "./attempt-worktree.js";
|
|
25
25
|
import { applyRunnerToolPath, installRunnerService, uninstallRunnerService } from "./service.js";
|
|
26
26
|
import { BRIDGE_PROTOCOL_VERSION, cachedBridgePreflight, gateRehearsalLockHeld, planGateRehearsals, PREFLIGHT_CACHE_TTL_MS, runGateRehearsal, unavailableWorkspacePreflight } from "./preflight.js";
|
|
@@ -667,9 +667,17 @@ async function runner() {
|
|
|
667
667
|
await heartbeat(client, config, workspace, currentBrief, preflight, intentProof, bootstrapResult);
|
|
668
668
|
}
|
|
669
669
|
await renewLeases(client, config);
|
|
670
|
+
// Dispatched work outranks a rehearsal already running. Stop it with nothing recorded, release
|
|
671
|
+
// the lock, and claim on this same cycle. The rehearsal stays due.
|
|
672
|
+
if (gateRehearsalRunning && await assignmentsPending(client)) {
|
|
673
|
+
gateRehearsalAbort?.abort();
|
|
674
|
+
await gateRehearsalDone;
|
|
675
|
+
}
|
|
670
676
|
// Another runner on this computer rehearsing its gate: claim as usual, rehearse on a later cycle.
|
|
677
|
+
// An assignment already waiting is claimed below; this cycle does not start a rehearsal.
|
|
671
678
|
if (workspace && onlineDriverIds(config).length && !gateRehearsalRunning && rehearsalPlan?.due
|
|
672
|
-
&& running.size === 0 && Object.keys(config.activeAttempts).length === 0 && !gateRehearsalLockHeld()
|
|
679
|
+
&& running.size === 0 && Object.keys(config.activeAttempts).length === 0 && !gateRehearsalLockHeld()
|
|
680
|
+
&& !await assignmentsPending(client)) {
|
|
673
681
|
startGateRehearsal(rehearsalPlan.due.workspace, rehearsalPlan.due.plan);
|
|
674
682
|
}
|
|
675
683
|
// No claim while the gate rehearses: the rehearsal measures the gate alone, never beside an attempt.
|
|
@@ -729,8 +737,10 @@ async function runner() {
|
|
|
729
737
|
await new Promise((resolveSleep) => setTimeout(resolveSleep, intervalMs));
|
|
730
738
|
}
|
|
731
739
|
}
|
|
732
|
-
/** True while the gate rehearsal runs in the background
|
|
740
|
+
/** True while the gate rehearsal runs in the background. A dispatched assignment aborts it, then this cycle claims. */
|
|
733
741
|
let gateRehearsalRunning = false;
|
|
742
|
+
let gateRehearsalAbort = null;
|
|
743
|
+
let gateRehearsalDone = Promise.resolve();
|
|
734
744
|
/** An observation fingerprints the source checkout; a fast-forward under it reads as the observer writing. */
|
|
735
745
|
let investigationRunning = false;
|
|
736
746
|
/** The rehearsals the heartbeat reports, one per on-shift repository; empty reports no field. */
|
|
@@ -777,21 +787,30 @@ async function rehearsalCheckouts(workspace, brief, refresh) {
|
|
|
777
787
|
*/
|
|
778
788
|
function startGateRehearsal(workspace, plan) {
|
|
779
789
|
gateRehearsalRunning = true;
|
|
790
|
+
const abort = new AbortController();
|
|
791
|
+
gateRehearsalAbort = abort;
|
|
780
792
|
const rehearsalPath = rehearsalWorktreePath(workspace);
|
|
781
793
|
console.log(`Rehearsing the gate in ${workspace}: ${plan.commands.join(", ")}`);
|
|
782
|
-
|
|
794
|
+
gateRehearsalDone = createRehearsalWorktree(workspace, plan.commit)
|
|
783
795
|
.then((path) => runGateRehearsal(path, plan, {
|
|
796
|
+
signal: abort.signal,
|
|
784
797
|
freshWorkspace: (warmUpSeconds) => {
|
|
785
798
|
console.log(`Gate warm-up took ${warmUpSeconds} s; measuring again in a fresh worktree`);
|
|
786
799
|
return createRehearsalWorktree(workspace, plan.commit);
|
|
787
800
|
},
|
|
788
801
|
}))
|
|
789
|
-
.then((rehearsal) => {
|
|
790
|
-
|
|
802
|
+
.then((rehearsal) => {
|
|
803
|
+
if (rehearsal === "busy")
|
|
804
|
+
console.log("Gate rehearsal deferred: another runner on this computer is rehearsing");
|
|
805
|
+
else if (abort.signal.aborted)
|
|
806
|
+
console.log("Gate rehearsal stopped: an assignment is waiting");
|
|
807
|
+
})
|
|
791
808
|
.catch((error) => console.error(`Gate rehearsal failed: ${redactSecrets(error instanceof Error ? error.message : "unknown error")}`))
|
|
792
809
|
.finally(async () => {
|
|
793
810
|
await removeAttemptWorktree(workspace, rehearsalPath);
|
|
794
811
|
gateRehearsalRunning = false;
|
|
812
|
+
if (gateRehearsalAbort === abort)
|
|
813
|
+
gateRehearsalAbort = null;
|
|
795
814
|
});
|
|
796
815
|
}
|
|
797
816
|
/** Last state announced, so a workspace that stays diverged does not print on every cycle. */
|
package/dist/execution.js
CHANGED
|
@@ -35,7 +35,7 @@ import { boundedTail, captureVerificationFailure, DeliveryReportDefectError, det
|
|
|
35
35
|
import { ensureChangeEvidence } from "./ensure-change-evidence.js";
|
|
36
36
|
import { ensureDemonstrationEvidence } from "./ensure-demonstration-evidence.js";
|
|
37
37
|
import { ensureNormativeEvidence } from "./ensure-normative-evidence.js";
|
|
38
|
-
import { changedPathsSince, compareWorktreeState, diffStatSince, worktreeStateFingerprint, worktreeWrittenPaths } from "./git-witness.js";
|
|
38
|
+
import { changedPathsSince, compareWorktreeState, diffStatSince, mergeBaseWithDefaultBranch, trackedDefaultBranchRef, worktreeStateFingerprint, worktreeWrittenPaths } from "./git-witness.js";
|
|
39
39
|
import { assertNormativeRefsMaterialized, checkNormativeMarkers, materializeNormativeRefs, NORMATIVE_MARKER_PREFIX, NORMATIVE_REF_PREFIX, parseNormativeRefs, } from "./normative-refs.js";
|
|
40
40
|
import { checkIntegrationObligations, INTEGRATION_OBLIGATION_PREFIX, parseIntegrationObligations, } from "./integration-obligations.js";
|
|
41
41
|
/** Keep a Mac awake only while an assignment is active; display sleep remains allowed. */
|
|
@@ -431,8 +431,9 @@ const executionContractSchema = z.object({
|
|
|
431
431
|
repository_fingerprint: z.string().nullable().optional().default(null),
|
|
432
432
|
requested_base_commit: z.string().nullable().optional().default(null),
|
|
433
433
|
claimed_head: z.string().nullable().optional().default(null),
|
|
434
|
-
//
|
|
435
|
-
//
|
|
434
|
+
// The control plane may carry the first revision's resolved attempt start. Rework and resume
|
|
435
|
+
// measure from the default-branch fork point; this field is the fallback when that fork
|
|
436
|
+
// point cannot be computed.
|
|
436
437
|
delivery_base_commit: z.string().nullable().optional().default(null),
|
|
437
438
|
source_attempt_id: z.string().uuid().nullable().optional().default(null),
|
|
438
439
|
// The change request and head commit the returned delivery left behind. A rework that keeps the
|
|
@@ -832,6 +833,21 @@ export async function pumpExecutionSlots(client, config, workspace, brief, timeo
|
|
|
832
833
|
}
|
|
833
834
|
return progressed;
|
|
834
835
|
}
|
|
836
|
+
/**
|
|
837
|
+
* True when `/runner/v1/assignments` lists work for this machine. A failed poll is not waiting
|
|
838
|
+
* work: the caller keeps the current cycle. The claim path still parses and claims.
|
|
839
|
+
*/
|
|
840
|
+
export async function assignmentsPending(client) {
|
|
841
|
+
try {
|
|
842
|
+
const data = await client.request("/runner/v1/assignments");
|
|
843
|
+
if (!data || typeof data !== "object" || !("assignments" in data))
|
|
844
|
+
return false;
|
|
845
|
+
return Array.isArray(data.assignments) && data.assignments.length > 0;
|
|
846
|
+
}
|
|
847
|
+
catch {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
835
851
|
/** Claim one assignment when under capacity; does not start the agent (multi-slot pump does). */
|
|
836
852
|
export async function claimNextAssignment(client, config, workspace, brief, driverId, processOnlineIds, options = {}) {
|
|
837
853
|
if (Object.keys(config.activeAttempts).length >= config.leaseCapacity)
|
|
@@ -1216,21 +1232,30 @@ async function runAttempt(client, config, driver, workspace, brief, taskId, watc
|
|
|
1216
1232
|
await removeAttemptWorktree(workspace, attemptWorkspace).catch(() => undefined);
|
|
1217
1233
|
}
|
|
1218
1234
|
};
|
|
1219
|
-
// T136: a
|
|
1220
|
-
//
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1223
|
-
//
|
|
1224
|
-
//
|
|
1225
|
-
//
|
|
1226
|
-
//
|
|
1227
|
-
//
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1235
|
+
// T136: a fresh attempt witnesses from the commit the worktree checked out. When main has
|
|
1236
|
+
// moved past the requested base, that resolved start is the claimed head; diffing from the
|
|
1237
|
+
// requested base would report commits that predate the agent as its work.
|
|
1238
|
+
// A rework or a resumed delivery measures from the merge-base of HEAD with origin/<branch>,
|
|
1239
|
+
// where <branch> is the branch the source checkout has checked out — the branch ensureCheckout
|
|
1240
|
+
// tracks. fetch updates that ref. fetch does not update origin/HEAD. Contract
|
|
1241
|
+
// delivery_base_commit and requested_base_commit apply only when that merge-base cannot be
|
|
1242
|
+
// computed. A contract base from the first attempt's start counts later default-branch commits
|
|
1243
|
+
// as the agent's writes (2026-10-23, task eff1253c). Diagnosis uses the same rule.
|
|
1244
|
+
const reworkOrResume = isReworkOrResumeDelivery({
|
|
1245
|
+
deliveryState: task.delivery_state,
|
|
1246
|
+
priorHeadCommit: executionContract.prior_head_commit,
|
|
1247
|
+
resumeFromAttemptId: executionContract.resume_from_attempt_id,
|
|
1248
|
+
});
|
|
1249
|
+
const deliveryBaseCommit = await resolveDeliveryBaseCommit({
|
|
1250
|
+
worktree: attemptWorkspace,
|
|
1251
|
+
sourceWorkspace: workspace,
|
|
1252
|
+
reworkOrResume,
|
|
1253
|
+
worktreeStart,
|
|
1254
|
+
taskId,
|
|
1255
|
+
deliveryBaseCommit: executionContract.delivery_base_commit,
|
|
1256
|
+
requestedBaseCommit: executionContract.requested_base_commit,
|
|
1257
|
+
repositoryBaseCommit: spec.repository?.base_commit,
|
|
1258
|
+
});
|
|
1234
1259
|
// Only when the worktree really stands at the timed-out run's head: a computer that cannot reach
|
|
1235
1260
|
// that commit starts at the base (resolveAttemptStartCommit), and the agent must not be told the
|
|
1236
1261
|
// work is there.
|
|
@@ -2993,6 +3018,37 @@ function referenceKind(kind) {
|
|
|
2993
3018
|
* Implemented in git-witness.ts; re-exported for control-plane test pins and investigation.
|
|
2994
3019
|
*/
|
|
2995
3020
|
export { changedPathsSince } from "./git-witness.js";
|
|
3021
|
+
/**
|
|
3022
|
+
* A rework or a resumed delivery, from the signals the attempt contract carries.
|
|
3023
|
+
*
|
|
3024
|
+
* Diagnosis is not a separate case: the same three signals select the base.
|
|
3025
|
+
*/
|
|
3026
|
+
export function isReworkOrResumeDelivery(input) {
|
|
3027
|
+
return input.deliveryState === "changes_requested"
|
|
3028
|
+
|| Boolean(input.priorHeadCommit)
|
|
3029
|
+
|| Boolean(input.resumeFromAttemptId);
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Commit that changedPathsSince, diffStatSince, and the land checks measure from.
|
|
3033
|
+
*
|
|
3034
|
+
* A fresh attempt keeps the worktree start. A rework or a resumed delivery uses the merge-base
|
|
3035
|
+
* of HEAD with `origin/<branch>` on the source checkout. Contract bases apply only when that
|
|
3036
|
+
* merge-base cannot be computed. One log line names the base this attempt used.
|
|
3037
|
+
*/
|
|
3038
|
+
export async function resolveDeliveryBaseCommit(input) {
|
|
3039
|
+
const ref = input.reworkOrResume ? await trackedDefaultBranchRef(input.sourceWorkspace) : null;
|
|
3040
|
+
const fork = ref ? await mergeBaseWithDefaultBranch(input.worktree, ref) : null;
|
|
3041
|
+
const base = !input.reworkOrResume
|
|
3042
|
+
? input.worktreeStart
|
|
3043
|
+
: fork
|
|
3044
|
+
?? input.deliveryBaseCommit
|
|
3045
|
+
?? input.requestedBaseCommit
|
|
3046
|
+
?? input.repositoryBaseCommit
|
|
3047
|
+
?? input.worktreeStart;
|
|
3048
|
+
const via = fork && ref ? ` (merge-base with ${ref})` : "";
|
|
3049
|
+
console.log(`Assignment ${input.taskId} delivery base ${base}${via}`);
|
|
3050
|
+
return base;
|
|
3051
|
+
}
|
|
2996
3052
|
/**
|
|
2997
3053
|
* Does this attempt earn a verification dry run?
|
|
2998
3054
|
*
|
package/dist/git-witness.js
CHANGED
|
@@ -21,6 +21,44 @@ async function gitFields(worktree, args) {
|
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Fork point of HEAD with the fetched default-branch ref.
|
|
26
|
+
*
|
|
27
|
+
* Null when the ref is missing or the two histories do not meet. Callers then use the
|
|
28
|
+
* contract base. A merge of that ref into the attempt branch moves this commit forward,
|
|
29
|
+
* so the diff still names only the branch's own files.
|
|
30
|
+
*/
|
|
31
|
+
export async function mergeBaseWithDefaultBranch(worktree, defaultBranchRef) {
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await execFileAsync("git", ["-C", worktree, "merge-base", "HEAD", defaultBranchRef], { timeout: 30_000, maxBuffer: 1_000_000 });
|
|
34
|
+
const sha = stdout.trim().toLowerCase();
|
|
35
|
+
return /^[0-9a-f]{7,64}$/.test(sha) ? sha : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `origin/<branch>` for the branch this checkout has checked out.
|
|
43
|
+
*
|
|
44
|
+
* `ensureCheckout` leaves that branch checked out. `fetchOrigin` and `refreshSourceWorkspace`
|
|
45
|
+
* update `origin/<branch>`. They do not update `origin/HEAD`, so this function never reads it.
|
|
46
|
+
* Null when HEAD is detached or `origin/<branch>` is absent; callers then use the contract base.
|
|
47
|
+
*/
|
|
48
|
+
export async function trackedDefaultBranchRef(workspace) {
|
|
49
|
+
try {
|
|
50
|
+
const { stdout } = await execFileAsync("git", ["-C", workspace, "rev-parse", "--abbrev-ref", "HEAD"], { timeout: 30_000, maxBuffer: 1_000_000 });
|
|
51
|
+
const branch = stdout.trim();
|
|
52
|
+
if (!branch || branch === "HEAD")
|
|
53
|
+
return null;
|
|
54
|
+
const ref = `origin/${branch}`;
|
|
55
|
+
await execFileAsync("git", ["-C", workspace, "rev-parse", "--verify", "--quiet", `${ref}^{commit}`], { timeout: 30_000, maxBuffer: 1_000_000 });
|
|
56
|
+
return ref;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
24
62
|
/**
|
|
25
63
|
* Paths changed on the attempt branch since `startCommit`, including uncommitted work.
|
|
26
64
|
* Null when git cannot answer — callers must not treat null as failure.
|
package/dist/preflight.js
CHANGED
|
@@ -10,7 +10,7 @@ import { bridgeConfigDirectory } from "./config.js";
|
|
|
10
10
|
import { runBoundedVerificationCommand, VERIFICATION_TIMEOUT_MS, verificationEvidenceCommands } from "./ensure-test-evidence.js";
|
|
11
11
|
import { lockfileDigest } from "./execution-facts.js";
|
|
12
12
|
import { resolveAgentTimeout, resolveMaxTurns } from "./execution-budget.js";
|
|
13
|
-
import { hasAntigravityLogin, hasClaudeLogin, hasGrokLogin, hasOpenAiLogin, hasOpenCodeLogin, driverExecutable, readSignInStatus, } from "./driver.js";
|
|
13
|
+
import { hasAntigravityLogin, hasClaudeLogin, hasGrokLogin, hasOpenAiLogin, hasOpenCodeLogin, driverExecutable, readSignInStatus, stopTrackedProcessGroups, } from "./driver.js";
|
|
14
14
|
import { laneQuota, laneTierBindings, localFuelOnlyDriver, onlineDriverIds, resolveDriverFuel, supportsReadOnlyDiagnosis } from "./drivers.js";
|
|
15
15
|
/** Protocol 3: read-only diagnosis can reuse a retained failed-attempt worktree. */
|
|
16
16
|
// 4: each driver snapshot reports diagnosis_read_only, so the control plane can require a
|
|
@@ -421,23 +421,70 @@ async function acquireGateRehearsalLock(lockPath) {
|
|
|
421
421
|
await unlink(claim).catch(() => undefined);
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
|
+
/** Resolve when `work` settles, or with "aborted" when `signal` aborts first. */
|
|
425
|
+
function untilAbort(work, signal) {
|
|
426
|
+
if (!signal)
|
|
427
|
+
return work;
|
|
428
|
+
if (signal.aborted)
|
|
429
|
+
return Promise.resolve("aborted");
|
|
430
|
+
return new Promise((resolve, reject) => {
|
|
431
|
+
const onAbort = () => resolve("aborted");
|
|
432
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
433
|
+
work.then((value) => {
|
|
434
|
+
signal.removeEventListener("abort", onAbort);
|
|
435
|
+
resolve(signal.aborted ? "aborted" : value);
|
|
436
|
+
}, (error) => {
|
|
437
|
+
signal.removeEventListener("abort", onAbort);
|
|
438
|
+
if (signal.aborted)
|
|
439
|
+
resolve("aborted");
|
|
440
|
+
else
|
|
441
|
+
reject(error);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/** Call `stop` if `signal` aborts after this returns. An already-aborted signal has no command yet. */
|
|
446
|
+
function stopWhenAborted(signal, stop) {
|
|
447
|
+
if (!signal || signal.aborted)
|
|
448
|
+
return () => undefined;
|
|
449
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
450
|
+
return () => signal.removeEventListener("abort", stop);
|
|
451
|
+
}
|
|
452
|
+
async function runRehearsalCommand(run, command, cwd, timeoutMs, signal) {
|
|
453
|
+
if (signal?.aborted)
|
|
454
|
+
return "aborted";
|
|
455
|
+
const work = (signal ? run(command, cwd, timeoutMs, signal) : run(command, cwd, timeoutMs)).catch(() => null);
|
|
456
|
+
const result = await untilAbort(work, signal);
|
|
457
|
+
if (result === "aborted" || signal?.aborted)
|
|
458
|
+
return "aborted";
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
424
461
|
/**
|
|
425
|
-
* Run the planned gate once and remember it for the day. Null when no command could start
|
|
426
|
-
*
|
|
427
|
-
*
|
|
462
|
+
* Run the planned gate once and remember it for the day. Null when no command could start, or when
|
|
463
|
+
* `signal` aborts: the command is stopped, nothing is remembered, and the lock is released, so the
|
|
464
|
+
* gate stays due. "busy" when another rehearsal on this computer holds the lock: nothing is run or
|
|
465
|
+
* remembered, so the gate is still due and the next cycle tries again.
|
|
428
466
|
*/
|
|
429
467
|
export async function runGateRehearsal(workspace, plan, options = {}) {
|
|
468
|
+
if (options.signal?.aborted)
|
|
469
|
+
return null;
|
|
430
470
|
const lockPath = options.lockPath ?? gateRehearsalLockPath();
|
|
431
471
|
if (!await acquireGateRehearsalLock(lockPath))
|
|
432
472
|
return "busy";
|
|
473
|
+
const releaseAbort = stopWhenAborted(options.signal, stopTrackedProcessGroups);
|
|
433
474
|
try {
|
|
475
|
+
if (options.signal?.aborted)
|
|
476
|
+
return null;
|
|
434
477
|
return await rehearseGate(workspace, plan, options);
|
|
435
478
|
}
|
|
436
479
|
finally {
|
|
480
|
+
releaseAbort();
|
|
437
481
|
await unlink(lockPath).catch(() => undefined);
|
|
438
482
|
}
|
|
439
483
|
}
|
|
440
484
|
async function rehearseGate(workspace, plan, options) {
|
|
485
|
+
const signal = options.signal;
|
|
486
|
+
if (signal?.aborted)
|
|
487
|
+
return null;
|
|
441
488
|
const now = options.now ?? Date.now;
|
|
442
489
|
const run = options.runCommand ?? runBoundedVerificationCommand;
|
|
443
490
|
const commands = [];
|
|
@@ -445,7 +492,10 @@ async function rehearseGate(workspace, plan, options) {
|
|
|
445
492
|
for (const command of plan.commands) {
|
|
446
493
|
const started = now();
|
|
447
494
|
// A command that cannot start measured nothing; the dry run skips it the same way.
|
|
448
|
-
|
|
495
|
+
// An abort measures nothing either: the gate stays due for the next quiet cycle.
|
|
496
|
+
const result = await runRehearsalCommand(run, command, cwd, REHEARSAL_TIMEOUT_MS, signal);
|
|
497
|
+
if (result === "aborted")
|
|
498
|
+
return null;
|
|
449
499
|
if (!result)
|
|
450
500
|
continue;
|
|
451
501
|
const elapsed = now() - started;
|
|
@@ -456,10 +506,16 @@ async function rehearseGate(workspace, plan, options) {
|
|
|
456
506
|
}
|
|
457
507
|
// The pass fit only the rehearsal's limit: it was a warm-up. Measure once more, in a fresh
|
|
458
508
|
// checkout with the attempt's limit, so the report shows what an attempt will see.
|
|
459
|
-
if (options.freshWorkspace)
|
|
460
|
-
|
|
509
|
+
if (options.freshWorkspace) {
|
|
510
|
+
const fresh = await untilAbort(options.freshWorkspace(seconds), signal);
|
|
511
|
+
if (fresh === "aborted")
|
|
512
|
+
return null;
|
|
513
|
+
cwd = fresh;
|
|
514
|
+
}
|
|
461
515
|
const again = now();
|
|
462
|
-
const second = await run
|
|
516
|
+
const second = await runRehearsalCommand(run, command, cwd, VERIFICATION_TIMEOUT_MS, signal);
|
|
517
|
+
if (second === "aborted")
|
|
518
|
+
return null;
|
|
463
519
|
if (!second) {
|
|
464
520
|
commands.push({ command, seconds, exit_code: result.code, timed_out: false });
|
|
465
521
|
continue;
|
|
@@ -472,6 +528,8 @@ async function rehearseGate(workspace, plan, options) {
|
|
|
472
528
|
warm_up_seconds: seconds,
|
|
473
529
|
});
|
|
474
530
|
}
|
|
531
|
+
if (signal?.aborted)
|
|
532
|
+
return null;
|
|
475
533
|
const at = now();
|
|
476
534
|
const rehearsal = commands.length
|
|
477
535
|
? { repository: plan.repository, commit: plan.commit, rehearsed_at: new Date(at).toISOString(), commands }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraland-labs/conduit-bridge",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.190",
|
|
4
4
|
"description": "Conduit Bridge CLI — join, connect, disconnect, multi-driver lanes, and run Claude Code / Codex / Cursor / OpenCode / Pi / Kiro / Antigravity / Grok Build agents for a Conduit organization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|