@miraland-labs/conduit-bridge 0.16.109 → 0.16.111
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 +14 -2
- package/dist/execution.js +1 -1
- package/dist/investigation.js +182 -4
- package/dist/preflight.js +32 -6
- package/package.json +1 -1
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
|
|
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
|
|
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
|
}
|
package/dist/execution.js
CHANGED
|
@@ -2909,7 +2909,7 @@ export async function runDeliveryVerificationGates(input) {
|
|
|
2909
2909
|
return results;
|
|
2910
2910
|
}
|
|
2911
2911
|
/** Return the worktree to its commit: tracked files restored, new files removed, ignored files kept. */
|
|
2912
|
-
async function discardWorktreeWrites(worktree) {
|
|
2912
|
+
export async function discardWorktreeWrites(worktree) {
|
|
2913
2913
|
const options = { timeout: 60_000, maxBuffer: 2_000_000 };
|
|
2914
2914
|
await execFileAsync("git", ["-C", worktree, "checkout", "--", "."], options).catch(() => undefined);
|
|
2915
2915
|
await execFileAsync("git", ["-C", worktree, "clean", "-fd"], options).catch(() => undefined);
|
package/dist/investigation.js
CHANGED
|
@@ -6,18 +6,22 @@
|
|
|
6
6
|
* the worktree it reads is deleted whichever way the run ends.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { boundedTail } from "./ensure-test-evidence.js";
|
|
9
|
+
import { boundedTail, runBoundedVerificationCommand } from "./ensure-test-evidence.js";
|
|
10
10
|
import { createAttemptWorktree, removeAttemptWorktree } from "./attempt-worktree.js";
|
|
11
11
|
import { buildWorkspaceBrief, ensureCommitAvailable, normalizeRepositoryUrl } from "./brief.js";
|
|
12
12
|
import { headCommitOrNull } from "./git.js";
|
|
13
13
|
import { redactSecrets } from "./config.js";
|
|
14
|
-
import { learnDriverFuel, resolveAssignmentModel, changedPathsSince, runDeliveryVerificationGates } from "./execution.js";
|
|
14
|
+
import { learnDriverFuel, resolveAssignmentModel, changedPathsSince, runDeliveryVerificationGates, discardWorktreeWrites } from "./execution.js";
|
|
15
15
|
import { DRIVERS, extractAgentReportJsonText, parseJsonObjectCandidate } from "./driver.js";
|
|
16
16
|
import { pickDriverForClaim, resolveDriverFuel, supportsReadOnlyDiagnosis } from "./drivers.js";
|
|
17
17
|
import { diffStatSince } from "./git-witness.js";
|
|
18
18
|
import { switchManagedWorkspace } from "./on-shift-apply.js";
|
|
19
19
|
import { execFile } from "node:child_process";
|
|
20
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
20
23
|
import { promisify } from "node:util";
|
|
24
|
+
import { isRunnableVerificationCommand } from "./execution-class.js";
|
|
21
25
|
const execFileAsync = promisify(execFile);
|
|
22
26
|
const budgetSchema = z.object({
|
|
23
27
|
max_files: z.number().int().min(1).max(24).optional().default(8),
|
|
@@ -37,6 +41,8 @@ export const investigationAssignmentSchema = z.object({
|
|
|
37
41
|
source_attempt_id: z.string().uuid().nullable().optional().default(null),
|
|
38
42
|
/** Delivery verification: the task's acceptance strings. Absent or [] on a plain investigation. */
|
|
39
43
|
acceptance: z.array(z.string().trim().min(1).max(4_000)).max(20).optional().default([]),
|
|
44
|
+
/** Delivery verification: the brief's `[sN]` / `[kN]` lines. Absent or [] on a plain investigation. */
|
|
45
|
+
brief_keys: z.array(z.string().trim().min(1).max(4_000)).max(40).optional().default([]),
|
|
40
46
|
});
|
|
41
47
|
/**
|
|
42
48
|
* Enforcement is a vendor's claim; verification is ours. Both run on every investigation — this only
|
|
@@ -67,9 +73,24 @@ const investigationBriefSchema = z.object({
|
|
|
67
73
|
files_read: z.number().int().min(0).max(24).default(0),
|
|
68
74
|
bytes_read: z.number().int().min(0).max(512 * 1024).default(0),
|
|
69
75
|
commands_run: z.array(z.string().trim().min(1).max(200)).max(10).default([]),
|
|
76
|
+
/**
|
|
77
|
+
* K12: probe source the agent wrote. Bridge executes these; it never copies claimed results into
|
|
78
|
+
* step_witness. Unknown keys such as a claimed exit_code are stripped.
|
|
79
|
+
*/
|
|
80
|
+
probes: z.array(z.object({
|
|
81
|
+
key: z.string().trim().min(1).max(40),
|
|
82
|
+
test: z.string().max(300).optional(),
|
|
83
|
+
lang: z.enum(["ts", "py", "sh"]).optional(),
|
|
84
|
+
source: z.string().max(8_000).optional(),
|
|
85
|
+
checks: z.string().max(500).optional().default(""),
|
|
86
|
+
})).max(20).default([]),
|
|
70
87
|
});
|
|
71
88
|
/** T122: a delivery verification is an investigation whose question carries this header. */
|
|
72
89
|
export const DELIVERY_VERIFICATION_HEADER = "DELIVERY VERIFICATION";
|
|
90
|
+
/** Owner-verbatim: the lane writes probe source; Bridge executes. Shown only when BRIEF KEYS are present. */
|
|
91
|
+
export const DELIVERY_VERIFICATION_PROBE_INSTRUCTION = 'For every BRIEF KEY that states behaviour, add one entry to probes: either {"key":"[sN]","test":"<registered command that asserts it>","checks":"<one line: what it proves>"} or {"key":"[sN]","lang":"ts|py|sh","source":"<a short script that exits 0 only when the key holds at this commit and non-zero otherwise; it may import repository modules by absolute path from the working directory>","checks":"<one line>"}. You do not run probes; Bridge runs them after you finish. Keys that state no behaviour (wording, scope, docs) get no probe.';
|
|
92
|
+
const BRIEF_JSON_EXAMPLE = '{"summary":"one paragraph: what the change does and whether it meets the criteria","findings":["grounded fact with file/symbol"],"verification":["command — result"],"limitations":["what you could not settle"],"criteria":[{"criterion":"c1","assessment":"supported|unsupported|unclear","reason":"what you saw"}],"command_failures":["command that exited non-zero — the decisive output line"],"files_read":0,"bytes_read":0,"commands_run":["command you actually ran"]}';
|
|
93
|
+
const BRIEF_JSON_EXAMPLE_WITH_PROBES = '{"summary":"one paragraph: what the change does and whether it meets the criteria","findings":["grounded fact with file/symbol"],"verification":["command — result"],"limitations":["what you could not settle"],"criteria":[{"criterion":"c1","assessment":"supported|unsupported|unclear","reason":"what you saw"}],"command_failures":["command that exited non-zero — the decisive output line"],"probes":[],"files_read":0,"bytes_read":0,"commands_run":["command you actually ran"]}';
|
|
73
94
|
export function isDeliveryVerification(assignment) {
|
|
74
95
|
return assignment.question.trimStart().startsWith(DELIVERY_VERIFICATION_HEADER);
|
|
75
96
|
}
|
|
@@ -110,6 +131,9 @@ export function buildDeliveryVerificationPrompt(assignment, commit, context) {
|
|
|
110
131
|
const commands = context.verificationCommands.length
|
|
111
132
|
? `REGISTERED COMMANDS (the only commands your shell accepts)\n${context.verificationCommands.map((command) => `- ${command}`).join("\n")}`
|
|
112
133
|
: "REGISTERED COMMANDS\n- none: this repository registers no verification command, so judge from the files only";
|
|
134
|
+
const keys = (assignment.brief_keys ?? []).map((key) => key.trim()).filter(Boolean);
|
|
135
|
+
const briefKeysBlock = keys.length ? `BRIEF KEYS\n${keys.join("\n")}` : "";
|
|
136
|
+
const probeBlock = keys.length ? DELIVERY_VERIFICATION_PROBE_INSTRUCTION : "";
|
|
113
137
|
return [
|
|
114
138
|
"You are Conductor's independent delivery verification. A coding agent delivered this commit against approved acceptance criteria; you check it the way a careful reviewer would, and you never take the agent's word for anything.",
|
|
115
139
|
"You have repo_read and test_run only. Do not edit, create, delete, format, commit, branch, push, install, or change any file.",
|
|
@@ -125,11 +149,15 @@ export function buildDeliveryVerificationPrompt(assignment, commit, context) {
|
|
|
125
149
|
`PINNED COMMIT ${commit}`,
|
|
126
150
|
commands,
|
|
127
151
|
assignment.question,
|
|
152
|
+
...(briefKeysBlock ? [briefKeysBlock] : []),
|
|
153
|
+
...(probeBlock ? [probeBlock] : []),
|
|
128
154
|
`BUDGET\nAt most ${assignment.budget.max_files} files read in full; the change summary and command output are not counted.`,
|
|
129
155
|
"",
|
|
130
156
|
"Return only one fenced ```json object with exactly this shape:",
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
keys.length ? BRIEF_JSON_EXAMPLE_WITH_PROBES : BRIEF_JSON_EXAMPLE,
|
|
158
|
+
keys.length
|
|
159
|
+
? "criteria has one entry per ACCEPTANCE key. probes has at most 20 entries, one per BRIEF KEY that states behaviour. Close the fence and add no prose after it."
|
|
160
|
+
: "criteria has one entry per ACCEPTANCE key. Close the fence and add no prose after it.",
|
|
133
161
|
].join("\n");
|
|
134
162
|
}
|
|
135
163
|
function formatGatesRunBlock(gates) {
|
|
@@ -230,6 +258,146 @@ export async function verifyObservationOnly(input) {
|
|
|
230
258
|
}
|
|
231
259
|
return { ok: true };
|
|
232
260
|
}
|
|
261
|
+
const PROBE_TIMEOUT_MS = 60_000;
|
|
262
|
+
const PROBE_MAX_BUFFER_BYTES = 2_000_000;
|
|
263
|
+
const PROBE_TAIL_CHARS = 1_000;
|
|
264
|
+
const PROBE_MAX = 20;
|
|
265
|
+
const PROBE_COMMAND_MAX = 300;
|
|
266
|
+
const PROBE_CHECKS_MAX = 500;
|
|
267
|
+
const PROBE_KEY_MAX = 40;
|
|
268
|
+
function probeEnv() {
|
|
269
|
+
const env = {};
|
|
270
|
+
if (process.env.PATH !== undefined)
|
|
271
|
+
env.PATH = process.env.PATH;
|
|
272
|
+
if (process.env.HOME !== undefined)
|
|
273
|
+
env.HOME = process.env.HOME;
|
|
274
|
+
return env;
|
|
275
|
+
}
|
|
276
|
+
function probeInterpreter(language) {
|
|
277
|
+
if (language === "ts")
|
|
278
|
+
return "node --import tsx";
|
|
279
|
+
if (language === "py")
|
|
280
|
+
return "python3";
|
|
281
|
+
return "bash";
|
|
282
|
+
}
|
|
283
|
+
function probeFilename(language) {
|
|
284
|
+
if (language === "ts")
|
|
285
|
+
return "probe.ts";
|
|
286
|
+
if (language === "py")
|
|
287
|
+
return "probe.py";
|
|
288
|
+
return "probe.sh";
|
|
289
|
+
}
|
|
290
|
+
function probeArgv(language, file) {
|
|
291
|
+
if (language === "ts")
|
|
292
|
+
return ["node", "--import", "tsx", file];
|
|
293
|
+
if (language === "py")
|
|
294
|
+
return ["python3", file];
|
|
295
|
+
return ["bash", file];
|
|
296
|
+
}
|
|
297
|
+
function clipWitnessField(value, max) {
|
|
298
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
299
|
+
}
|
|
300
|
+
async function execProbe(argv, workspace) {
|
|
301
|
+
const bin = argv[0];
|
|
302
|
+
if (!bin)
|
|
303
|
+
throw new Error("Probe command is empty");
|
|
304
|
+
try {
|
|
305
|
+
const { stdout, stderr } = await execFileAsync(bin, argv.slice(1), {
|
|
306
|
+
cwd: workspace,
|
|
307
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
308
|
+
maxBuffer: PROBE_MAX_BUFFER_BYTES,
|
|
309
|
+
env: probeEnv(),
|
|
310
|
+
});
|
|
311
|
+
return { stdout: String(stdout), stderr: String(stderr), code: 0 };
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
const err = error;
|
|
315
|
+
if (typeof err.code === "string")
|
|
316
|
+
throw error;
|
|
317
|
+
return {
|
|
318
|
+
stdout: String(err.stdout ?? ""),
|
|
319
|
+
stderr: String(err.stderr || err.message || "probe failed"),
|
|
320
|
+
code: err.killed === true || typeof err.code !== "number" ? -1 : err.code,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function witnessRecord(input) {
|
|
325
|
+
return {
|
|
326
|
+
key: clipWitnessField(input.key, PROBE_KEY_MAX),
|
|
327
|
+
kind: input.kind,
|
|
328
|
+
command: clipWitnessField(input.command, PROBE_COMMAND_MAX),
|
|
329
|
+
exit_code: input.exitCode,
|
|
330
|
+
tail: boundedTail(`${input.stderr.trim()}\n${input.stdout.trim()}`.trim(), PROBE_TAIL_CHARS),
|
|
331
|
+
checks: clipWitnessField(input.checks, PROBE_CHECKS_MAX),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Run at most twenty agent-authored probes after the read-only proof. Records only what Bridge
|
|
336
|
+
* executed; agent-claimed exit codes never enter the witness.
|
|
337
|
+
*/
|
|
338
|
+
export async function runDeliveryVerificationProbes(input) {
|
|
339
|
+
const selected = [...input.probes].slice(0, PROBE_MAX);
|
|
340
|
+
const witnesses = [];
|
|
341
|
+
for (const probe of selected) {
|
|
342
|
+
const checks = probe.checks ?? "";
|
|
343
|
+
const named = probe.test?.trim() ?? "";
|
|
344
|
+
const source = probe.source?.trim() ?? "";
|
|
345
|
+
if (named) {
|
|
346
|
+
if (!isRunnableVerificationCommand(named)) {
|
|
347
|
+
witnesses.push(witnessRecord({
|
|
348
|
+
key: probe.key, kind: "test", command: named, exitCode: -1, stdout: "", stderr: "not a registered command", checks,
|
|
349
|
+
}));
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
let exitCode = -1;
|
|
353
|
+
let stdout = "";
|
|
354
|
+
let stderr = "";
|
|
355
|
+
try {
|
|
356
|
+
const result = await (input.runCommand ?? runBoundedVerificationCommand)(named, input.workspace);
|
|
357
|
+
exitCode = result.code;
|
|
358
|
+
stdout = result.stdout;
|
|
359
|
+
stderr = result.stderr;
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
stderr = error instanceof Error ? error.message : String(error);
|
|
363
|
+
exitCode = -1;
|
|
364
|
+
}
|
|
365
|
+
witnesses.push(witnessRecord({
|
|
366
|
+
key: probe.key, kind: "test", command: named, exitCode, stdout, stderr, checks,
|
|
367
|
+
}));
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (!source || !probe.lang)
|
|
371
|
+
continue;
|
|
372
|
+
const language = probe.lang;
|
|
373
|
+
const command = `${probeInterpreter(language)} ${probe.key}`;
|
|
374
|
+
let dir = null;
|
|
375
|
+
let exitCode = -1;
|
|
376
|
+
let stdout = "";
|
|
377
|
+
let stderr = "";
|
|
378
|
+
try {
|
|
379
|
+
dir = await mkdtemp(join(tmpdir(), "conduit-probe-"));
|
|
380
|
+
const file = join(dir, probeFilename(language));
|
|
381
|
+
await writeFile(file, source, { encoding: "utf8" });
|
|
382
|
+
const result = await execProbe(probeArgv(language, file), input.workspace);
|
|
383
|
+
exitCode = result.code;
|
|
384
|
+
stdout = result.stdout;
|
|
385
|
+
stderr = result.stderr;
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
stderr = error instanceof Error ? error.message : String(error);
|
|
389
|
+
exitCode = -1;
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
if (dir)
|
|
393
|
+
await rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
|
394
|
+
}
|
|
395
|
+
witnesses.push(witnessRecord({
|
|
396
|
+
key: probe.key, kind: "probe", command, exitCode, stdout, stderr, checks,
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
return witnesses;
|
|
400
|
+
}
|
|
233
401
|
async function settle(client, investigationId, body) {
|
|
234
402
|
await client.request(`/runner/v1/investigations/${investigationId}/settle`, {
|
|
235
403
|
method: "POST",
|
|
@@ -393,6 +561,15 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
393
561
|
console.error(`Investigation ${assignment.id} discarded — ${check.error}: ${check.detail}`);
|
|
394
562
|
return true;
|
|
395
563
|
}
|
|
564
|
+
const stepWitness = deliveryVerification
|
|
565
|
+
? await runDeliveryVerificationProbes({
|
|
566
|
+
workspace: attemptWorkspace,
|
|
567
|
+
probes: observed.probes ?? [],
|
|
568
|
+
runCommand: options.runProbeCommand,
|
|
569
|
+
})
|
|
570
|
+
: [];
|
|
571
|
+
if (deliveryVerification)
|
|
572
|
+
await discardWorktreeWrites(attemptWorkspace);
|
|
396
573
|
await settle(client, assignment.id, {
|
|
397
574
|
lease_token: leaseToken,
|
|
398
575
|
status: "completed",
|
|
@@ -405,6 +582,7 @@ export async function executeNextInvestigation(client, config, workspace, brief,
|
|
|
405
582
|
command_failures: deliveryVerification
|
|
406
583
|
? gatesRun.filter((gate) => gate.exitCode !== 0).map(gateFailureFact).slice(0, 10)
|
|
407
584
|
: observed.command_failures,
|
|
585
|
+
step_witness: stepWitness,
|
|
408
586
|
witness: {
|
|
409
587
|
repository_fingerprint: assignment.repository_fingerprint ?? liveRepository ?? "unknown",
|
|
410
588
|
commit,
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.16.111",
|
|
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": {
|