litmus-cli 1.4.67 → 1.4.69

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.
@@ -270,6 +270,29 @@ export interface NativeEditor {
270
270
  * has to fail.
271
271
  */
272
272
  export declare const NATIVE_EDITORS: Record<NativeIde, NativeEditor>;
273
+ /**
274
+ * ENG-2332 — PATH first, then the places the installer puts it.
275
+ *
276
+ * The old code asked only whether `ed.bin` was on PATH, and answered a "no" with a
277
+ * manual command that begins with `ed.bin` — i.e. told the candidate to run the binary
278
+ * whose absence was the error. On the machine that reported this, the binary was sitting
279
+ * at `/Applications/Cursor.app/Contents/Resources/app/bin/cursor` throughout.
280
+ *
281
+ * PATH still wins when it answers, because that is the command the candidate's own
282
+ * tooling and any support answer will use. A bundle path is a fallback, and it is
283
+ * probed with `probeEditorCli` rather than trusted on `existsSync` alone: a file at the
284
+ * expected path that cannot execute is not a route, and offering it as one would replace
285
+ * one silent failure with another.
286
+ *
287
+ * The build it reported rides back on the result, so the seed check below reads the
288
+ * version THIS command printed. A second `--version` spawn could answer for a different
289
+ * binary — a PATH that changed under us, a shim that dispatches — and would then be
290
+ * compared against a workspace on behalf of an editor we are not about to launch.
291
+ */
292
+ interface ResolvedEditor extends ResolvedEditorCommand {
293
+ version: EditorVersion;
294
+ }
295
+ export declare function resolveEditorCommand(ed: NativeEditor): ResolvedEditor | null;
273
296
  /**
274
297
  * ENG-2152 — what the editor CLI we are about to drive says it IS.
275
298
  *
@@ -403,6 +426,20 @@ export declare function editorLaunchFailedLines(ed: NativeEditor, alias: string,
403
426
  * has no workspace files to list.
404
427
  */
405
428
  export declare function editorUnverifiedLines(ed: NativeEditor, alias: string, pageUrl: string | null): string[];
429
+ /**
430
+ * Ensure the Remote-SSH extension is installed in the user's editor before we
431
+ * open the remote window (the `gh codespace code` model). Best-effort: a
432
+ * marketplace/network failure returns "failed" rather than aborting connect —
433
+ * the caller tells the user how to install it by hand. Assumes the editor's CLI
434
+ * has been resolved (caller gates on `resolveEditorCommand`).
435
+ */
436
+ /**
437
+ * ENG-3158 — `<command> --list-extensions` output, or null when it cannot be read. A
438
+ * null skips the conflict check rather than stopping connect: the check only refuses on
439
+ * evidence, never on a failed read.
440
+ */
441
+ export declare function listEditorExtensions(command: string): string | null;
442
+ export declare function ensureRemoteSshExtension(ed: NativeEditor, command: string, listed: string | null): "present" | "installed" | "failed";
406
443
  /**
407
444
  * ENG-2332 — where the editor's own output goes.
408
445
  *
@@ -505,6 +542,7 @@ export declare function editorLaunchOutputLines(logPath: string, launchLog: stri
505
542
  * never as "there is no server" — see `verifyVerdict`.
506
543
  */
507
544
  export type WorkspaceProbeRunner = (remoteCommand: string) => string | null;
545
+ export declare function sshProbeRunner(alias: string, executable?: string): WorkspaceProbeRunner;
508
546
  export declare function verifyEditorAttached(ed: NativeEditor, baseline: WorkspaceProbe | null, run: WorkspaceProbeRunner, onWait: (secondsWaited: number) => void, launched: EditorLaunchHandle, clientCommit?: string | null): Promise<VerifyVerdict>;
509
547
  /** Whether the editor's Remote-SSH extension was already there, put there, or could not be. */
510
548
  export type RemoteSshExtensionState = "present" | "installed" | "failed";
@@ -698,6 +736,8 @@ export declare function chatConfigActionForIdentity(ide: IdeChoice, state: "conf
698
736
  */
699
737
  export declare function browserFallback(hasUrl: boolean, ideWasChosen: boolean): "browser" | "vscode" | "unavailable";
700
738
  export interface ConnectOptions {
739
+ /** ENG-3217: opt-in desktop experiment; never proof of agent attachment. */
740
+ cursorAgentWindow?: boolean;
701
741
  /**
702
742
  * ENG-2332 item 4 — print what the editor wrote while opening, and where that log is.
703
743
  * The output is CAPTURED either way (a failed verification prints its tail whether or
@@ -1 +1 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,10 @@
1
+ import { requestCursorAgentWindow, cursorAgentWindowLines } from "../lib/cursor-agent-window.js";
2
+ import { ConnectionAttempt } from "../lib/connection-attempt.js";
3
+ import { updateSshConfig } from "../lib/ssh-config-file.js";
1
4
  import os from "os";
2
5
  import path from "path";
3
6
  import readline from "readline";
4
- import { closeSync, constants, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from "fs";
7
+ import { closeSync, constants, existsSync, mkdirSync, openSync, readFileSync } from "fs";
5
8
  import { randomBytes } from "crypto";
6
9
  import { execFileSync, spawn, spawnSync } from "child_process";
7
10
  import chalk from "chalk";
@@ -320,13 +323,9 @@ function ensureSshKey(keyPath) {
320
323
  stdio: "ignore",
321
324
  });
322
325
  }
323
- function writeSshConfig(alias, block, controlBlock) {
326
+ async function writeSshConfig(alias, block, controlBlock) {
324
327
  const cfgPath = path.join(os.homedir(), ".ssh", "config");
325
- const dir = path.dirname(cfgPath);
326
- if (!existsSync(dir))
327
- mkdirSync(dir, { recursive: true, mode: 0o700 });
328
- const existing = existsSync(cfgPath) ? readFileSync(cfgPath, "utf8") : "";
329
- writeFileSync(cfgPath, upsertSshConfigContent(existing, alias, block, controlBlock), { mode: 0o600 });
328
+ await updateSshConfig(cfgPath, (existing) => upsertSshConfigContent(existing, alias, block, controlBlock));
330
329
  return cfgPath;
331
330
  }
332
331
  /**
@@ -412,7 +411,7 @@ function probeEditorCli(command) {
412
411
  return null;
413
412
  }
414
413
  }
415
- function resolveEditorCommand(ed) {
414
+ export function resolveEditorCommand(ed) {
416
415
  const onPath = probeEditorCli(ed.bin);
417
416
  if (onPath)
418
417
  return { command: ed.bin, source: "path", version: onPath };
@@ -687,7 +686,7 @@ export function editorUnverifiedLines(ed, alias, pageUrl) {
687
686
  * null skips the conflict check rather than stopping connect: the check only refuses on
688
687
  * evidence, never on a failed read.
689
688
  */
690
- function listEditorExtensions(command) {
689
+ export function listEditorExtensions(command) {
691
690
  const list = editorSpawnSpec(command, ["--list-extensions"], process.platform);
692
691
  if (!list)
693
692
  return null;
@@ -699,22 +698,13 @@ function listEditorExtensions(command) {
699
698
  return null;
700
699
  }
701
700
  }
702
- function ensureRemoteSshExtension(ed, command) {
703
- const list = editorSpawnSpec(command, ["--list-extensions"], process.platform);
704
- // Undrivable on this platform — the same outcome as an install that would not run.
705
- // `failed` is already best-effort and never aborts connect.
706
- if (!list)
701
+ export function ensureRemoteSshExtension(ed, command, listed) {
702
+ // An unreadable inventory is not evidence of absence; don't mutate it blindly.
703
+ if (listed === null)
707
704
  return "failed";
708
- try {
709
- const listed = spawnSync(list.command, list.args, { encoding: "utf8", shell: list.shell, timeout: 60000 });
710
- if (!listed.error && (listed.stdout ?? "").split(/\r?\n/).some((l) => l.trim().toLowerCase() === ed.remoteSshExt)) {
711
- return "present";
712
- }
713
- }
714
- catch {
715
- // Couldn't list (unexpected) — fall through and try to install anyway.
716
- }
717
- const install = editorSpawnSpec(command, ["--install-extension", ed.remoteSshExt, "--force"], process.platform);
705
+ if (listed.split(/\r?\n/).some((l) => l.trim().toLowerCase() === ed.remoteSshExt))
706
+ return "present";
707
+ const install = editorSpawnSpec(command, ["--install-extension", ed.remoteSshExt], process.platform);
718
708
  if (!install)
719
709
  return "failed";
720
710
  try {
@@ -868,11 +858,11 @@ function readEditorLaunchLog(logPath) {
868
858
  return null;
869
859
  }
870
860
  }
871
- function sshProbeRunner(alias) {
861
+ export function sshProbeRunner(alias, executable = "ssh") {
872
862
  return (remoteCommand) => {
873
863
  try {
874
- const probed = spawnSync("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-o", "LogLevel=ERROR", alias, remoteCommand], { encoding: "utf8", timeout: 20000 });
875
- if (probed.error)
864
+ const probed = spawnSync(executable, ["-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-o", "LogLevel=ERROR", alias, remoteCommand], { encoding: "utf8", timeout: 20000 });
865
+ if (probed.error || probed.status !== 0)
876
866
  return null;
877
867
  return probed.stdout ?? null;
878
868
  }
@@ -1252,8 +1242,7 @@ function pruneStaleHostKeys(p) {
1252
1242
  // known_hosts as [host]:port, so a bare-host -R would miss it.
1253
1243
  if (p.port)
1254
1244
  targets.push(`[${p.host}]:${p.port}`);
1255
- if (p.jumpHost)
1256
- targets.push(p.jumpPort ? `[${p.jumpHost}]:${p.jumpPort}` : p.jumpHost);
1245
+ // The shared bastion is not reprovisioned with this workspace. Preserve its pinned key.
1257
1246
  for (const t of targets) {
1258
1247
  try {
1259
1248
  execFileSync("ssh-keygen", ["-R", t], { stdio: "ignore" });
@@ -1280,18 +1269,34 @@ export async function runConnect(token, ideOpt, opts = {}) {
1280
1269
  // printed last, on every way out of the flow, so it neither delays the editor nor
1281
1270
  // is lost when a later step fails. The workspace lines follow it, so the candidate's
1282
1271
  // last read is where they are and what the session ending looks like.
1272
+ setErrorContext({ token, apiBase: resolveApiBase(process.env.LITMUS_API_URL) });
1273
+ const attempt = new ConnectionAttempt();
1283
1274
  const skillImport = { report: null, footer: [], sshAlias: null, publishedPorts: [] };
1275
+ // Register before entering the flow: a fast fatal report can settle before the
1276
+ // caller's finally begins, especially while an async SSH config write is unwinding.
1277
+ let releaseExitHold;
1278
+ holdExitFor(new Promise((resolve) => { releaseExitHold = resolve; }));
1284
1279
  try {
1285
- await connectFlow(token, ideOpt, opts, skillImport);
1280
+ await connectFlow(token, ideOpt, opts, skillImport, attempt);
1281
+ }
1282
+ catch (error) {
1283
+ attempt.finish("failed");
1284
+ attempt.evidence(error instanceof Error ? error.message : null);
1285
+ throw error;
1286
1286
  }
1287
1287
  finally {
1288
- // Held, so a `fatal` in the flow waits for the upload and its report (ENG-2919).
1289
- await holdExitFor((async () => {
1288
+ try {
1289
+ await attempt.flush();
1290
+ if (opts.verbose || attempt.needsSupport)
1291
+ console.log(chalk.dim(` Connection ID: ${attempt.id}`));
1290
1292
  if (skillImport.report)
1291
1293
  await finishSkillImport(skillImport.report);
1292
1294
  for (const line of skillImport.footer)
1293
1295
  console.log(line);
1294
- })());
1296
+ }
1297
+ finally {
1298
+ releaseExitHold();
1299
+ }
1295
1300
  }
1296
1301
  // The ssh route's session runs last, so nothing we print lands inside the remote shell.
1297
1302
  if (!skillImport.sshAlias)
@@ -1323,7 +1328,7 @@ async function openSshRoute(alias) {
1323
1328
  if (status)
1324
1329
  process.exitCode = status;
1325
1330
  }
1326
- async function connectFlow(token, ideOpt, opts, skillImport) {
1331
+ async function connectFlow(token, ideOpt, opts, skillImport, attempt) {
1327
1332
  const verbose = Boolean(opts.verbose);
1328
1333
  const apiBase = resolveApiBase(process.env.LITMUS_API_URL);
1329
1334
  setErrorContext({ token, apiBase });
@@ -1338,6 +1343,8 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1338
1343
  spinner.succeed(meta.assessmentName);
1339
1344
  }
1340
1345
  catch (err) {
1346
+ attempt.evidence(workspaceStartNetworkDetail(err));
1347
+ attempt.evidence(err instanceof Error ? err.message : null);
1341
1348
  // ENG-2895: the Error line below says what failed; a second "Connection failed" line
1342
1349
  // above it was the same fact twice, and wrong when the cause was a rejected token.
1343
1350
  spinner.stop();
@@ -1360,28 +1367,37 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1360
1367
  // ENG-2890 — the assessment page is the one re-entry route connect prints: its URL
1361
1368
  // carries only the assessment id, never the token.
1362
1369
  const pageUrl = candidatePageUrl(apiBase, meta.assessmentId);
1370
+ attempt.stage("selection");
1363
1371
  const { ide: chosenIde, chosen: ideWasChosen } = await resolveIde(ideOpt, console.log);
1364
1372
  let ide = chosenIde;
1373
+ attempt.editor(ide);
1365
1374
  // ENG-2814: asked here, next to the editor choice, and uploaded once the workspace is
1366
1375
  // ready. Never fails the connect. See `lib/connect-skills.ts`.
1376
+ attempt.stage("skills");
1367
1377
  const pendingSkills = await offerSkillImport({
1368
1378
  backendUrl, token, flag: opts.skills, verbose,
1369
1379
  });
1380
+ attempt.stage("ssh_key");
1370
1381
  // 2. Ensure a local SSH key (private key never leaves this machine).
1371
1382
  try {
1372
1383
  ensureSshKey(KEY_PATH);
1373
1384
  }
1374
1385
  catch (err) {
1386
+ attempt.evidence(workspaceStartNetworkDetail(err));
1387
+ attempt.evidence(err instanceof Error ? err.message : null);
1375
1388
  fatal("Couldn't create an SSH key.", "Install OpenSSH (it includes ssh-keygen), then run the same litmus connect command again.", { internalDetail: err instanceof Error ? err.message : String(err), severity: "warning" });
1376
1389
  return;
1377
1390
  }
1378
1391
  const pubkey = readFileSync(`${KEY_PATH}.pub`, "utf8").trim();
1379
1392
  // 3. Start the workspace with our public key.
1393
+ attempt.stage("workspace_start");
1380
1394
  const boot = ora("Starting your workspace...").start();
1381
1395
  try {
1382
1396
  await startWorkspace(backendUrl, token, pubkey);
1383
1397
  }
1384
1398
  catch (err) {
1399
+ attempt.evidence(workspaceStartNetworkDetail(err));
1400
+ attempt.evidence(err instanceof Error ? err.message : null);
1385
1401
  // The Error line below names what went wrong; see the sign-in spinner above.
1386
1402
  boot.stop();
1387
1403
  const code = err instanceof ServerError ? err.errorCode : undefined;
@@ -1437,6 +1453,8 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1437
1453
  return;
1438
1454
  }
1439
1455
  boot.succeed("Workspace ready");
1456
+ attempt.stage("workspace_ready");
1457
+ attempt.checkpoint();
1440
1458
  if (pendingSkills)
1441
1459
  skillImport.report = holdExitFor(startSkillImport(backendUrl, token, pendingSkills, undefined, () => ide));
1442
1460
  // Browser IDE: no local SSH / VS Code needed — just open the signed URL.
@@ -1454,6 +1472,7 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1454
1472
  if (fallback === "vscode") {
1455
1473
  warn("The browser IDE isn't available here. Opening VS Code instead.");
1456
1474
  ide = "vscode";
1475
+ attempt.editor(ide);
1457
1476
  }
1458
1477
  else if (fallback === "unavailable") {
1459
1478
  fatal("The browser IDE isn't available for this workspace yet.", "Open it in VS Code instead: run the same litmus connect command with --ide vscode.", { severity: "warning" });
@@ -1461,6 +1480,8 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1461
1480
  }
1462
1481
  }
1463
1482
  if (ide === "browser" && conn.browser_ide_url) {
1483
+ attempt.stage("browser_open");
1484
+ attempt.finish("browser_requested");
1464
1485
  // ENG-3137 — the workspace now trusts this machine's key, so the assessment page's
1465
1486
  // Plain SSH offers `ssh <alias>`. Write the entry that alias names, as every other
1466
1487
  // route does. Best-effort: a config we could not write must not cost the browser route.
@@ -1469,11 +1490,13 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1469
1490
  const parsed = parseSshCommand(conn.ssh_command);
1470
1491
  const alias = aliasFor(conn.ssh_hostname, token);
1471
1492
  pruneStaleHostKeys(parsed);
1472
- const cfgPath = writeSshConfig(alias, buildSshConfigBlock(alias, KEY_PATH, parsed), buildControlPathBlock(alias, parsed));
1493
+ const cfgPath = await writeSshConfig(alias, buildSshConfigBlock(alias, KEY_PATH, parsed), buildControlPathBlock(alias, parsed));
1473
1494
  if (verbose)
1474
1495
  info(`SSH config written to ${cfgPath} (Host "${alias}")`);
1475
1496
  }
1476
1497
  catch (err) {
1498
+ attempt.evidence(workspaceStartNetworkDetail(err));
1499
+ attempt.evidence(err instanceof Error ? err.message : null);
1477
1500
  if (verbose)
1478
1501
  info(`SSH config not written: ${err instanceof Error ? err.message : String(err)}`);
1479
1502
  }
@@ -1499,6 +1522,13 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1499
1522
  // Prune stale host keys first so a re-provisioned container's new key is
1500
1523
  // accepted, not refused (ENG-1221).
1501
1524
  const parsed = parseSshCommand(conn.ssh_command);
1525
+ // Reuse this observation across compatibility checks, preflight and diagnostics.
1526
+ const sshBanners = new Map();
1527
+ const sshBanner = (executable) => {
1528
+ if (!sshBanners.has(executable))
1529
+ sshBanners.set(executable, readSshVersionBanner(executable));
1530
+ return sshBanners.get(executable) ?? null;
1531
+ };
1502
1532
  // ENG-2682 — Windows' own OpenSSH below 8.1 cannot run the ProxyJump every bastion
1503
1533
  // route depends on (see `lib/ssh-client.ts`). Refuse before writing anything, with the
1504
1534
  // version named and a way out, rather than hand an editor a connection it will lose
@@ -1506,17 +1536,18 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1506
1536
  // `remote.SSH.path` when it names one, PATH's ssh otherwise.
1507
1537
  if (process.platform === "win32" && parsed.jumpHost && ide !== "browser") {
1508
1538
  const executable = sshExecutableFor(ide, ide === "ssh" ? null : readEditorSettings(ide));
1509
- const client = parseOpenSshVersion(readSshVersionBanner(executable) ?? "");
1539
+ const client = parseOpenSshVersion(sshBanner(executable) ?? "");
1510
1540
  if (client && proxyJumpBroken(client)) {
1511
1541
  const where = executable === "ssh" ? "" : ` at ${executable}, set in remote.SSH.path`;
1512
1542
  fatal(`Your Windows SSH (${client.label}${where}) is too old to reach the workspace.`, oldWindowsSshHint(Boolean(conn.browser_ide_url)), { severity: "warning" });
1513
1543
  return;
1514
1544
  }
1515
1545
  }
1546
+ attempt.stage("ssh_config");
1516
1547
  const alias = aliasFor(conn.ssh_hostname, token);
1517
1548
  pruneStaleHostKeys(parsed);
1518
1549
  const block = buildSshConfigBlock(alias, KEY_PATH, parsed);
1519
- const cfgPath = writeSshConfig(alias, block, buildControlPathBlock(alias, parsed));
1550
+ const cfgPath = await writeSshConfig(alias, block, buildControlPathBlock(alias, parsed));
1520
1551
  if (verbose)
1521
1552
  info(`SSH config written to ${cfgPath} (Host "${alias}")`);
1522
1553
  // ENG-2693 — prove the connection before handing it off (see `sshPreflight`). The
@@ -1529,19 +1560,23 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1529
1560
  // Called per route, not here once: the native route runs it only AFTER the WSL check
1530
1561
  // (Greptile on #2844), so a candidate whose Windows editor cannot see WSL's config is
1531
1562
  // told that, rather than a generic ssh failure from the wrong side of the pairing.
1563
+ const sshExecutable = sshExecutableFor(ide, ide === "ssh" ? null : readEditorSettings(ide));
1532
1564
  const preflightStops = async (route) => {
1533
- const sshExecutable = sshExecutableFor(route, route === "ssh" ? null : readEditorSettings(route));
1534
1565
  // ENG-2895: up to two 45s attempts, so it gets a spinner; it says nothing when it works.
1535
1566
  const checking = ora("Checking the connection to your workspace...").start();
1567
+ attempt.stage("ssh_preflight");
1568
+ attempt.fact("sshSelection", sshExecutable === "ssh" ? "path" : "editor-setting");
1569
+ attempt.fact("sshVersion", parseOpenSshVersion(sshBanner(sshExecutable) ?? "")?.label);
1536
1570
  const preflight = await sshPreflight(alias, sshExecutable);
1537
1571
  checking.stop();
1538
1572
  if (preflight.ok)
1539
1573
  return false;
1574
+ attempt.evidence(preflight.detail.join("\n"));
1540
1575
  fatal("Your computer couldn't open an SSH connection to the workspace.", sshPreflightHint(preflight.detail, Boolean(conn.browser_ide_url), verbose), {
1541
1576
  severity: "error",
1542
1577
  internalDetail: [
1543
1578
  `route=${route} platform=${process.platform} ssh=${sshExecutable}`,
1544
- `client=${readSshVersionBanner(sshExecutable) ?? "unreadable"}`,
1579
+ `client=${sshBanner(sshExecutable) ?? "unreadable"}`,
1545
1580
  ...preflight.detail,
1546
1581
  ].join("\n"),
1547
1582
  });
@@ -1552,6 +1587,8 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1552
1587
  if (ide === "ssh") {
1553
1588
  if (await preflightStops("ssh"))
1554
1589
  return;
1590
+ attempt.stage("ssh_handoff");
1591
+ attempt.finish("ssh_ready");
1555
1592
  if (verbose)
1556
1593
  console.log(chalk.dim(` (equivalently: ${conn.ssh_command})`));
1557
1594
  if (opts.tunnel) {
@@ -1577,7 +1614,12 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1577
1614
  // places its installer puts it, because "not on PATH" was being reported as "not
1578
1615
  // installed" on a machine where the binary was sitting in the application bundle the
1579
1616
  // whole time. Null here means we genuinely could not find a way to drive it.
1617
+ attempt.stage("editor_resolution");
1580
1618
  const resolved = resolveEditorCommand(editor);
1619
+ attempt.editor(ide, resolved?.version.version);
1620
+ attempt.fact("editorFound", Boolean(resolved));
1621
+ attempt.fact("wsl", isWsl());
1622
+ attempt.fact("editorCommit", resolved?.version.commit);
1581
1623
  // ENG-2683 — inside WSL, the editor on PATH is usually the WINDOWS app, and its
1582
1624
  // Remote-SSH reads Windows' ssh config, not the WSL one written above (see
1583
1625
  // `lib/wsl.ts`). Launching it would fail in the editor with nothing on our side, so say
@@ -1599,7 +1641,9 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1599
1641
  // With nothing resolved there is nothing to probe, and `unverified` is what that means:
1600
1642
  // it is the absence branch's job to say so, below the mismatch refusal, so a candidate
1601
1643
  // whose `code` is Cursor is told THAT rather than that their editor is missing.
1644
+ attempt.stage("editor_resolution");
1602
1645
  const identity = resolved ? identifyEditorCli(editor, resolved.command) : { kind: "unverified" };
1646
+ attempt.fact("editorIdentity", identity.kind);
1603
1647
  // 5b. Point this machine's Copilot Chat at the Litmus gateway (ENG-1475).
1604
1648
  //
1605
1649
  // ONLY ON THE NATIVE VS CODE PATH, and that is the whole reason it is here rather
@@ -1726,9 +1770,15 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1726
1770
  // ENG-3158 — before the workspace read and the launch, so a candidate
1727
1771
  // whose editor would take the connection with the wrong Remote-SSH loses no open Cursor
1728
1772
  // windows for a launch that cannot hold. Read-only: nothing is installed or removed.
1773
+ attempt.stage("extensions");
1774
+ const listed = listEditorExtensions(resolved.command);
1775
+ attempt.fact("extensionInventory", listed === null ? "unavailable" : "read");
1776
+ attempt.fact("requiredExtension", listed === null ? "unknown" : listed.split(/\r?\n/).includes(editor.remoteSshExt) ? "present" : "missing");
1777
+ if (listed !== null)
1778
+ attempt.fact("conflictingExtension", conflictingRemoteSshInstalled(editor, listed));
1729
1779
  if (editor.conflictingRemoteSshExt) {
1730
- const listed = listEditorExtensions(resolved.command);
1731
1780
  if (listed !== null && conflictingRemoteSshInstalled(editor, listed)) {
1781
+ attempt.finish("extension_conflict");
1732
1782
  const editorCmd = pasteableEditorCommand(editor, resolved, process.platform);
1733
1783
  for (const line of conflictingRemoteSshLines(editor, editorCmd, alias, pageUrl))
1734
1784
  console.log(line);
@@ -1740,7 +1790,7 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1740
1790
  // it. Both, and their order, live in `attemptEditorLaunch`: the guarantee here is a
1741
1791
  // property of the SEQUENCE, so it is held in one testable unit rather than in the
1742
1792
  // arrangement of the lines below. Everything this block still owns is output.
1743
- const probe = sshProbeRunner(alias);
1793
+ const probe = sshProbeRunner(alias, sshExecutable);
1744
1794
  // Read once for both the seed check and attachment baseline.
1745
1795
  const readSpinner = ora(`Checking your workspace...`).start();
1746
1796
  const baseline = probeWorkspace(editor, probe, resolved.version.commit);
@@ -1780,7 +1830,8 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1780
1830
  clientVersion: resolved.version,
1781
1831
  ensureExtension: () => {
1782
1832
  const extSpinner = ora(`Checking ${editor.label} Remote-SSH extension...`).start();
1783
- const state = ensureRemoteSshExtension(editor, resolved.command);
1833
+ const state = ensureRemoteSshExtension(editor, resolved.command, listed);
1834
+ attempt.fact("extensionSetup", state);
1784
1835
  // ENG-2890: an internal step, so it says nothing when it worked.
1785
1836
  if (state === "failed") {
1786
1837
  extSpinner.warn(`Couldn't install ${editor.label}'s Remote-SSH extension. If ${editor.label} asks for it, install it from the Extensions view.`);
@@ -1795,6 +1846,8 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1795
1846
  },
1796
1847
  launchLogPath: editorLaunchLogPath,
1797
1848
  launch: (logPath) => {
1849
+ attempt.stage("editor_launch");
1850
+ attempt.checkpoint();
1798
1851
  const launched = launches.send(logPath);
1799
1852
  // The launch is not the result, so nothing claims one here. What used to be printed
1800
1853
  // at this point was `✔ Opening <editor> → <alias>`, which is true of a spawn that is
@@ -1804,18 +1857,21 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1804
1857
  return launched;
1805
1858
  },
1806
1859
  readLaunchLog: () => launches.readLog(readEditorLaunchLog),
1807
- verifyAttached: (baseline, launched) => verifyEditorAttached(editor, baseline, probe, (waited) => {
1808
- if (spinners.verify) {
1809
- spinners.verify.text = `Opening ${editor.label} in your workspace... (${waited}s)`;
1810
- }
1811
- // ENG-2884: if the first request still produced no attached window, ask the
1812
- // (now running) Cursor once more. A window already opening for this folder is
1813
- // focused rather than duplicated, so a slow first attach costs nothing.
1814
- if (shouldResendCursorOpen(editor.ide, cursorStartsFresh, waited, resend)) {
1815
- resend.done = true;
1816
- launches.send(editorLaunchLogPath());
1817
- }
1818
- }, launched, resolved.version.commit),
1860
+ verifyAttached: (baseline, launched) => {
1861
+ attempt.stage("editor_verify");
1862
+ return verifyEditorAttached(editor, baseline, probe, (waited) => {
1863
+ if (spinners.verify) {
1864
+ spinners.verify.text = `Opening ${editor.label} in your workspace... (${waited}s)`;
1865
+ }
1866
+ // ENG-2884: if the first request still produced no attached window, ask the
1867
+ // (now running) Cursor once more. A window already opening for this folder is
1868
+ // focused rather than duplicated, so a slow first attach costs nothing.
1869
+ if (shouldResendCursorOpen(editor.ide, cursorStartsFresh, waited, resend)) {
1870
+ resend.done = true;
1871
+ launches.send(editorLaunchLogPath());
1872
+ }
1873
+ }, launched, resolved.version.commit);
1874
+ },
1819
1875
  });
1820
1876
  if (outcome.kind === "refused") {
1821
1877
  for (const line of editorSeedMismatchLines(editor, outcome.seed, pageUrl, alias)) {
@@ -1825,6 +1881,9 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1825
1881
  }
1826
1882
  const launchSpinner = spinners.verify;
1827
1883
  if (outcome.kind === "launch-failed") {
1884
+ attempt.finish("launch_failed");
1885
+ attempt.evidence(outcome.error);
1886
+ attempt.evidence(outcome.launchLog);
1828
1887
  // No window was created, so none of the not-connected copy is true here. See
1829
1888
  // `editorLaunchFailedLines`.
1830
1889
  launchSpinner?.fail(`Couldn't start ${editor.label}.`);
@@ -1834,11 +1893,18 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1834
1893
  console.log(line);
1835
1894
  return;
1836
1895
  }
1896
+ attempt.evidence(outcome.launchLog);
1837
1897
  if (outcome.verdict.kind === "connected") {
1898
+ attempt.finish("server_detected");
1838
1899
  launchSpinner?.succeed(`${editor.label}'s server is running in your workspace.`);
1839
1900
  skillImport.footer.push(...editorConnectedLines(editor, alias, verbose));
1901
+ if (editor.ide === "cursor" && opts.cursorAgentWindow) {
1902
+ const handoff = requestCursorAgentWindow(resolved.command, alias, REMOTE_WORKSPACE_DIR);
1903
+ skillImport.footer.push(...cursorAgentWindowLines(handoff, verbose));
1904
+ }
1840
1905
  }
1841
1906
  else if (outcome.verdict.kind === "unverified") {
1907
+ attempt.finish("unverified");
1842
1908
  // Three-valued on purpose: we could not reach the workspace to ask, which is not the
1843
1909
  // same as knowing nothing came up, and saying either of the other two here would be a
1844
1910
  // claim we cannot make. See `verifyVerdict`.
@@ -1847,6 +1913,7 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
1847
1913
  console.log(line);
1848
1914
  }
1849
1915
  else {
1916
+ attempt.finish("not_connected");
1850
1917
  launchSpinner?.fail(`${editor.label} hasn't connected to your workspace yet.`);
1851
1918
  for (const line of editorNotConnectedLines(editor, alias, pageUrl, verbose))
1852
1919
  console.log(line);