@miraland-labs/conduit-bridge 0.16.186 → 0.16.187

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 CHANGED
@@ -780,7 +780,12 @@ function startGateRehearsal(workspace, plan) {
780
780
  const rehearsalPath = rehearsalWorktreePath(workspace);
781
781
  console.log(`Rehearsing the gate in ${workspace}: ${plan.commands.join(", ")}`);
782
782
  void createRehearsalWorktree(workspace, plan.commit)
783
- .then((path) => runGateRehearsal(path, plan))
783
+ .then((path) => runGateRehearsal(path, plan, {
784
+ freshWorkspace: (warmUpSeconds) => {
785
+ console.log(`Gate warm-up took ${warmUpSeconds} s; measuring again in a fresh worktree`);
786
+ return createRehearsalWorktree(workspace, plan.commit);
787
+ },
788
+ }))
784
789
  .then((rehearsal) => { if (rehearsal === "busy")
785
790
  console.log("Gate rehearsal deferred: another runner on this computer is rehearsing"); })
786
791
  .catch((error) => console.error(`Gate rehearsal failed: ${redactSecrets(error instanceof Error ? error.message : "unknown error")}`))
package/dist/preflight.js CHANGED
@@ -7,7 +7,8 @@ import { dirname, join } from "node:path";
7
7
  import { promisify } from "node:util";
8
8
  import { buildWorkspaceBrief, normalizeRepositoryUrl } from "./brief.js";
9
9
  import { bridgeConfigDirectory } from "./config.js";
10
- import { runBoundedVerificationCommand, verificationEvidenceCommands } from "./ensure-test-evidence.js";
10
+ import { runBoundedVerificationCommand, VERIFICATION_TIMEOUT_MS, verificationEvidenceCommands } from "./ensure-test-evidence.js";
11
+ import { lockfileDigest } from "./execution-facts.js";
11
12
  import { resolveAgentTimeout, resolveMaxTurns } from "./execution-budget.js";
12
13
  import { hasAntigravityLogin, hasClaudeLogin, hasGrokLogin, hasOpenAiLogin, hasOpenCodeLogin, driverExecutable, readSignInStatus, } from "./driver.js";
13
14
  import { laneQuota, laneTierBindings, localFuelOnlyDriver, onlineDriverIds, resolveDriverFuel, supportsReadOnlyDiagnosis } from "./drivers.js";
@@ -315,18 +316,29 @@ async function readGateRehearsals(statePath) {
315
316
  return {};
316
317
  }
317
318
  }
319
+ /**
320
+ * The rehearsal's own limit. The first run after a dependency change can compile a whole dependency
321
+ * tree: on 2026-10-22 acme-sim's registry crates took more than ten minutes, the attempt limit
322
+ * stopped the rehearsal mid-compile, the shared target directory stayed cold, and the next attempt
323
+ * stopped at the same limit before any agent started. The rehearsal absorbs that warm-up.
324
+ */
325
+ export const REHEARSAL_TIMEOUT_MS = 3_600_000;
318
326
  /** UTC calendar day: a push to main costs no run, and a new day measures the cache again. */
319
327
  function rehearsalDay(at) {
320
328
  return new Date(at).toISOString().slice(0, 10);
321
329
  }
322
- /** The last rehearsal of this workspace's repository, and the gate to run when one is due today. */
330
+ /**
331
+ * The last rehearsal of this workspace's repository, and the gate to run when one is due: once a
332
+ * day, and again when the workspace's lockfiles change, because new dependencies need a new warm-up.
333
+ */
323
334
  export async function planGateRehearsal(brief, options = {}) {
324
335
  const commands = verificationEvidenceCommands(brief.verification, brief.declared_verification, null);
325
336
  if (!brief.repository || !brief.base_commit || !commands.length)
326
337
  return { report: null, due: null };
327
338
  const repository = normalizeRepositoryUrl(brief.repository);
328
339
  const stored = (await readGateRehearsals(options.statePath ?? gateRehearsalStatePath()))[repository] ?? null;
329
- const digest = createHash("sha256").update(commands.join("\n")).digest("hex").slice(0, 16);
340
+ const lockfiles = options.workspace ? await lockfileDigest(options.workspace) : null;
341
+ const digest = createHash("sha256").update(commands.join("\n")).update(lockfiles ?? "").digest("hex").slice(0, 16);
330
342
  const day = rehearsalDay((options.now ?? Date.now)());
331
343
  if (stored && stored.day === day && stored.digest === digest)
332
344
  return { report: stored.rehearsal, due: null };
@@ -342,7 +354,7 @@ export async function planGateRehearsals(checkouts, options = {}) {
342
354
  const seen = new Set();
343
355
  let due = null;
344
356
  for (const checkout of checkouts) {
345
- const planned = await planGateRehearsal(checkout.brief, options);
357
+ const planned = await planGateRehearsal(checkout.brief, { ...options, workspace: checkout.workspace });
346
358
  const repository = planned.report?.repository ?? planned.due?.repository;
347
359
  if (!repository || seen.has(repository))
348
360
  continue;
@@ -429,13 +441,36 @@ async function rehearseGate(workspace, plan, options) {
429
441
  const now = options.now ?? Date.now;
430
442
  const run = options.runCommand ?? runBoundedVerificationCommand;
431
443
  const commands = [];
444
+ let cwd = workspace;
432
445
  for (const command of plan.commands) {
433
446
  const started = now();
434
447
  // A command that cannot start measured nothing; the dry run skips it the same way.
435
- const result = await run(command, workspace).catch(() => null);
448
+ const result = await run(command, cwd, REHEARSAL_TIMEOUT_MS).catch(() => null);
436
449
  if (!result)
437
450
  continue;
438
- commands.push({ command, seconds: Math.round((now() - started) / 1_000), exit_code: result.code, timed_out: result.timedOut === true });
451
+ const elapsed = now() - started;
452
+ const seconds = Math.round(elapsed / 1_000);
453
+ if (result.code !== 0 || result.timedOut === true || elapsed <= VERIFICATION_TIMEOUT_MS) {
454
+ commands.push({ command, seconds, exit_code: result.code, timed_out: result.timedOut === true });
455
+ continue;
456
+ }
457
+ // The pass fit only the rehearsal's limit: it was a warm-up. Measure once more, in a fresh
458
+ // checkout with the attempt's limit, so the report shows what an attempt will see.
459
+ if (options.freshWorkspace)
460
+ cwd = await options.freshWorkspace(seconds);
461
+ const again = now();
462
+ const second = await run(command, cwd, VERIFICATION_TIMEOUT_MS).catch(() => null);
463
+ if (!second) {
464
+ commands.push({ command, seconds, exit_code: result.code, timed_out: false });
465
+ continue;
466
+ }
467
+ commands.push({
468
+ command,
469
+ seconds: Math.round((now() - again) / 1_000),
470
+ exit_code: second.code,
471
+ timed_out: second.timedOut === true,
472
+ warm_up_seconds: seconds,
473
+ });
439
474
  }
440
475
  const at = now();
441
476
  const rehearsal = commands.length
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.186",
3
+ "version": "0.16.187",
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": {