@miraland-labs/conduit-bridge 0.16.110 → 0.16.112

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/driver.js CHANGED
@@ -7,6 +7,18 @@ import { mkdir, readFile, rm, rmdir, writeFile } from "node:fs/promises";
7
7
  import { join, resolve } from "node:path";
8
8
  import { z } from "zod";
9
9
  import { allowsExternalFetch, deniedCommands, executionClassPromptRules, isRunnableVerificationCommand, parsePiJsonl, projectClaude, projectCoarseMode, projectCodex, projectCursor, projectGrok, projectKiroTools, projectPi, requireStampedExecutionClass, } from "./execution-class.js";
10
+ /**
11
+ * Classify one Cursor `status` / Kiro `whoami` probe. Signed-out phrases win; an unanswered
12
+ * probe (timeout, spawn error, empty output, or text that names neither state) is unknown.
13
+ */
14
+ export function readSignInStatus(result, signedInPattern) {
15
+ const text = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
16
+ if (/not logged in|not authenticated|login required/i.test(text))
17
+ return "signed_out";
18
+ if (result.code === 0 && signedInPattern.test(text))
19
+ return "signed_in";
20
+ return "unknown";
21
+ }
10
22
  /**
11
23
  * The bound for this turn.
12
24
  *
@@ -1017,10 +1029,10 @@ export const cursorDriver = {
1017
1029
  return { status: "failed", resultText: null, sessionId: null, error: "cursor preflight found no bounded verification command for the test_run grant" };
1018
1030
  }
1019
1031
  // A ~/.cursor directory exists even after logout — ask the CLI itself.
1020
- // `agent status` exits 0 either way, so the text is the signal.
1032
+ // `agent status` exits 0 either way; only a definite signed_out fails closed.
1021
1033
  if (!process.env.CURSOR_API_KEY) {
1022
1034
  const status = await execute(executable, ["status"], input.workspace, 15_000, undefined, "local");
1023
- if (status.code !== 0 || !/logged in as/i.test(`${status.stdout}\n${status.stderr}`)) {
1035
+ if (readSignInStatus(status, /logged in as/i) === "signed_out") {
1024
1036
  return { status: "failed", resultText: null, sessionId: null, error: "cursor has no login (set CURSOR_API_KEY or run `agent login`)" };
1025
1037
  }
1026
1038
  }
@@ -18,11 +18,20 @@ import { diffStatSince } from "./git-witness.js";
18
18
  import { switchManagedWorkspace } from "./on-shift-apply.js";
19
19
  import { execFile } from "node:child_process";
20
20
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
21
+ import { createRequire } from "node:module";
21
22
  import { tmpdir } from "node:os";
22
23
  import { join } from "node:path";
23
24
  import { promisify } from "node:util";
24
25
  import { isRunnableVerificationCommand } from "./execution-class.js";
25
26
  const execFileAsync = promisify(execFile);
27
+ const tsxLoader = (() => {
28
+ try {
29
+ return createRequire(import.meta.url).resolve("tsx");
30
+ }
31
+ catch {
32
+ return "tsx";
33
+ }
34
+ })();
26
35
  const budgetSchema = z.object({
27
36
  max_files: z.number().int().min(1).max(24).optional().default(8),
28
37
  max_duration_ms: z.number().int().min(10_000).max(15 * 60_000).optional().default(5 * 60_000),
@@ -265,6 +274,29 @@ const PROBE_MAX = 20;
265
274
  const PROBE_COMMAND_MAX = 300;
266
275
  const PROBE_CHECKS_MAX = 500;
267
276
  const PROBE_KEY_MAX = 40;
277
+ const PROBE_HOLDS_LINE = "PROBE HOLDS";
278
+ const PROBE_FAILS_LINE = "PROBE FAILS";
279
+ function lastNonEmptyLine(text) {
280
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
281
+ return lines.at(-1) ?? "";
282
+ }
283
+ function sourceProbeVerdict(exitCode, stdout) {
284
+ const last = lastNonEmptyLine(stdout);
285
+ if (exitCode === 0 && last === PROBE_HOLDS_LINE)
286
+ return "holds";
287
+ if (exitCode === 1 && last === PROBE_FAILS_LINE)
288
+ return "fails";
289
+ return "broken";
290
+ }
291
+ function testProbeVerdict(ran, exitCode) {
292
+ if (!ran)
293
+ return "broken";
294
+ return exitCode === 0 ? "holds" : "fails";
295
+ }
296
+ function probeTail(stderr, stdout) {
297
+ const stderrTail = stderr.length > PROBE_TAIL_CHARS ? stderr.slice(stderr.length - PROBE_TAIL_CHARS) : stderr;
298
+ return boundedTail(`${stderrTail}\n${stdout}`, PROBE_TAIL_CHARS);
299
+ }
268
300
  function probeEnv() {
269
301
  const env = {};
270
302
  if (process.env.PATH !== undefined)
@@ -282,14 +314,14 @@ function probeInterpreter(language) {
282
314
  }
283
315
  function probeFilename(language) {
284
316
  if (language === "ts")
285
- return "probe.ts";
317
+ return "probe.mts";
286
318
  if (language === "py")
287
319
  return "probe.py";
288
320
  return "probe.sh";
289
321
  }
290
322
  function probeArgv(language, file) {
291
323
  if (language === "ts")
292
- return ["node", "--import", "tsx", file];
324
+ return ["node", "--import", tsxLoader, file];
293
325
  if (language === "py")
294
326
  return ["python3", file];
295
327
  return ["bash", file];
@@ -314,9 +346,11 @@ async function execProbe(argv, workspace) {
314
346
  const err = error;
315
347
  if (typeof err.code === "string")
316
348
  throw error;
349
+ const stdout = String(err.stdout ?? "");
350
+ const stderr = String(err.stderr ?? "");
317
351
  return {
318
- stdout: String(err.stdout ?? ""),
319
- stderr: String(err.stderr || err.message || "probe failed"),
352
+ stdout,
353
+ stderr: stdout === "" && stderr === "" ? String(err.message || "probe failed") : stderr,
320
354
  code: err.killed === true || typeof err.code !== "number" ? -1 : err.code,
321
355
  };
322
356
  }
@@ -327,8 +361,9 @@ function witnessRecord(input) {
327
361
  kind: input.kind,
328
362
  command: clipWitnessField(input.command, PROBE_COMMAND_MAX),
329
363
  exit_code: input.exitCode,
330
- tail: boundedTail(`${input.stderr.trim()}\n${input.stdout.trim()}`.trim(), PROBE_TAIL_CHARS),
364
+ tail: probeTail(input.stderr, input.stdout),
331
365
  checks: clipWitnessField(input.checks, PROBE_CHECKS_MAX),
366
+ verdict: input.verdict,
332
367
  };
333
368
  }
334
369
  /**
@@ -346,17 +381,20 @@ export async function runDeliveryVerificationProbes(input) {
346
381
  if (!isRunnableVerificationCommand(named)) {
347
382
  witnesses.push(witnessRecord({
348
383
  key: probe.key, kind: "test", command: named, exitCode: -1, stdout: "", stderr: "not a registered command", checks,
384
+ verdict: "broken",
349
385
  }));
350
386
  continue;
351
387
  }
352
388
  let exitCode = -1;
353
389
  let stdout = "";
354
390
  let stderr = "";
391
+ let ran = false;
355
392
  try {
356
393
  const result = await (input.runCommand ?? runBoundedVerificationCommand)(named, input.workspace);
357
394
  exitCode = result.code;
358
395
  stdout = result.stdout;
359
396
  stderr = result.stderr;
397
+ ran = true;
360
398
  }
361
399
  catch (error) {
362
400
  stderr = error instanceof Error ? error.message : String(error);
@@ -364,6 +402,7 @@ export async function runDeliveryVerificationProbes(input) {
364
402
  }
365
403
  witnesses.push(witnessRecord({
366
404
  key: probe.key, kind: "test", command: named, exitCode, stdout, stderr, checks,
405
+ verdict: testProbeVerdict(ran, exitCode),
367
406
  }));
368
407
  continue;
369
408
  }
@@ -394,6 +433,7 @@ export async function runDeliveryVerificationProbes(input) {
394
433
  }
395
434
  witnesses.push(witnessRecord({
396
435
  key: probe.key, kind: "probe", command, exitCode, stdout, stderr, checks,
436
+ verdict: sourceProbeVerdict(exitCode, stdout),
397
437
  }));
398
438
  }
399
439
  return witnesses;
package/dist/preflight.js CHANGED
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { promisify } from "node:util";
4
4
  import { buildWorkspaceBrief, normalizeRepositoryUrl } from "./brief.js";
5
5
  import { resolveMaxTurns } from "./execution-budget.js";
6
- import { hasAntigravityLogin, hasClaudeLogin, hasGrokLogin, hasOpenAiLogin, hasOpenCodeLogin, driverExecutable, } from "./driver.js";
6
+ import { hasAntigravityLogin, hasClaudeLogin, hasGrokLogin, hasOpenAiLogin, hasOpenCodeLogin, driverExecutable, readSignInStatus, } from "./driver.js";
7
7
  import { laneQuota, laneTierBindings, localFuelOnlyDriver, onlineDriverIds, resolveDriverFuel, supportsReadOnlyDiagnosis } from "./drivers.js";
8
8
  /** Protocol 3: read-only diagnosis can reuse a retained failed-attempt worktree. */
9
9
  // 4: each driver snapshot reports diagnosis_read_only, so the control plane can require a
@@ -128,7 +128,35 @@ async function driverCompatibilityFingerprint(driver, workspace, run) {
128
128
  features,
129
129
  })).digest("hex");
130
130
  }
131
- async function localAuthenticationReady(driver, workspace, run) {
131
+ /** Last definite Cursor/Kiro sign-in answer. Unknown never writes here. */
132
+ export const lastSignIn = new Map();
133
+ export function resetSignInMemoryForTests() {
134
+ lastSignIn.clear();
135
+ }
136
+ /** Retry once on unknown, then the last definite answer, then signed-in with a warning. */
137
+ export async function resolveSignInAnswer(driver, probe) {
138
+ const once = async () => {
139
+ try {
140
+ return await probe();
141
+ }
142
+ catch {
143
+ return "unknown";
144
+ }
145
+ };
146
+ let answer = await once();
147
+ if (answer === "unknown")
148
+ answer = await once();
149
+ if (answer !== "unknown") {
150
+ lastSignIn.set(driver, answer);
151
+ return answer;
152
+ }
153
+ const previous = lastSignIn.get(driver);
154
+ if (previous)
155
+ return previous;
156
+ console.warn(`sign-in unverified (${driver})`);
157
+ return "signed_in";
158
+ }
159
+ export async function localAuthenticationReady(driver, workspace, run) {
132
160
  if (driver === "claude-code")
133
161
  return hasClaudeLogin();
134
162
  if (driver === "codex")
@@ -142,14 +170,12 @@ async function localAuthenticationReady(driver, workspace, run) {
142
170
  if (driver === "cursor") {
143
171
  if (process.env.CURSOR_API_KEY)
144
172
  return true;
145
- const status = await run(driverExecutable(driver), ["status"], workspace);
146
- return status.code === 0 && /logged in as/i.test(`${status.stdout}\n${status.stderr}`);
173
+ return (await resolveSignInAnswer(driver, async () => readSignInStatus(await run(driverExecutable(driver), ["status"], workspace), /logged in as/i))) !== "signed_out";
147
174
  }
148
175
  if (driver === "kiro") {
149
176
  if (process.env.KIRO_API_KEY)
150
177
  return true;
151
- const status = await run(driverExecutable(driver), ["whoami"], workspace);
152
- return status.code === 0 && !/not logged in|not authenticated|login required/i.test(`${status.stdout}\n${status.stderr}`);
178
+ return (await resolveSignInAnswer(driver, async () => readSignInStatus(await run(driverExecutable(driver), ["whoami"], workspace), /\S/))) !== "signed_out";
153
179
  }
154
180
  // Pi stores provider auth in its own config; CLI has no stable whoami — version probe is enough.
155
181
  if (driver === "pi")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraland-labs/conduit-bridge",
3
- "version": "0.16.110",
3
+ "version": "0.16.112",
4
4
  "description": "Conduit Bridge CLI \u2014 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": {