litmus-cli 1.4.46 → 1.4.47
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/commands/connect.d.ts +110 -227
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +289 -517
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/init.d.ts +2 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +2 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/connect-skills.d.ts +5 -1
- package/dist/lib/connect-skills.d.ts.map +1 -1
- package/dist/lib/connect-skills.js +6 -2
- package/dist/lib/connect-skills.js.map +1 -1
- package/dist/lib/cursor-restart.d.ts +1 -1
- package/dist/lib/cursor-restart.js +1 -1
- package/dist/lib/editor-server.d.ts +12 -4
- package/dist/lib/editor-server.d.ts.map +1 -1
- package/dist/lib/editor-server.js +30 -4
- package/dist/lib/editor-server.js.map +1 -1
- package/dist/lib/session-end.d.ts +5 -8
- package/dist/lib/session-end.d.ts.map +1 -1
- package/dist/lib/session-end.js +6 -12
- package/dist/lib/session-end.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/connect.js
CHANGED
|
@@ -16,7 +16,7 @@ import { parseEditorVersion, parseWorkspaceProbe, remoteProbeCommand, seedVerdic
|
|
|
16
16
|
import { fatal, success, info, warn, setErrorContext } from "../lib/errors.js";
|
|
17
17
|
import { oldWindowsSshHint, parseOpenSshVersion, proxyJumpBroken, readEditorSettings, readSshVersionBanner, sshExecutableFor, sshPreflight, sshPreflightHint, } from "../lib/ssh-client.js";
|
|
18
18
|
import { editorEndingForewarning } from "../lib/session-end.js";
|
|
19
|
-
import {
|
|
19
|
+
import { attemptQuit, platformSupportsGracefulCursorRestart, realCursorProcessIo, } from "../lib/cursor-restart.js";
|
|
20
20
|
/**
|
|
21
21
|
* `litmus connect <token>` — the CLI connector for the v2 codespace, modeled on
|
|
22
22
|
* `gh codespace ssh/code`. It removes the manual "paste your public key + copy
|
|
@@ -371,7 +371,7 @@ export const NATIVE_EDITORS = {
|
|
|
371
371
|
downloadUrl: "https://cursor.com/download",
|
|
372
372
|
seedCommitPath: "/opt/cursor-server-cache/COMMIT",
|
|
373
373
|
serverHomeDir: ".cursor-server",
|
|
374
|
-
requiresSeedMatch:
|
|
374
|
+
requiresSeedMatch: false,
|
|
375
375
|
},
|
|
376
376
|
};
|
|
377
377
|
/**
|
|
@@ -492,344 +492,147 @@ export function identifyEditorCli(ed, command, run = runEditorHelp) {
|
|
|
492
492
|
return { kind: "unverified" };
|
|
493
493
|
return actual === ed.ide ? { kind: "match" } : { kind: "mismatch", actual };
|
|
494
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* ENG-2890 — the copy rules every connect outcome below follows.
|
|
497
|
+
*
|
|
498
|
+
* A hiring manager watched a candidate fail to open Cursor behind a ~41-line notice,
|
|
499
|
+
* quit and retry several times, and get nowhere. So each outcome is a few lines, the
|
|
500
|
+
* action first, with no mechanism words, and NO CREDENTIAL: the connect token and the
|
|
501
|
+
* browser-IDE URL (which carries `t=`) are never printed. The way back is "run the same
|
|
502
|
+
* litmus connect command again", which is in the candidate's shell history, and the
|
|
503
|
+
* assessment page, whose URL carries only the assessment id (`candidatePageUrl`).
|
|
504
|
+
*/
|
|
505
|
+
const SAME_COMMAND = "run the same litmus connect command";
|
|
506
|
+
/**
|
|
507
|
+
* The route that needs nothing else to work: the browser IDE, reached from the page,
|
|
508
|
+
* or the alias we have just written into their ~/.ssh/config.
|
|
509
|
+
*/
|
|
510
|
+
export function workNowLine(alias, pageUrl) {
|
|
511
|
+
const page = pageUrl
|
|
512
|
+
? `the browser IDE on your assessment page (${chalk.cyan(pageUrl)})`
|
|
513
|
+
: "the browser IDE on your assessment page";
|
|
514
|
+
return ` Or work now: ${page}, or ${chalk.cyan(`ssh ${alias}`)}.`;
|
|
515
|
+
}
|
|
516
|
+
/** Where the long form went. Not printed when it is already being shown. */
|
|
517
|
+
function detailsLine(verbose) {
|
|
518
|
+
return verbose ? [] : [chalk.dim(" Details: add --verbose.")];
|
|
519
|
+
}
|
|
495
520
|
/**
|
|
496
521
|
* ENG-2152 — the refusal, when the command the candidate's PATH answers with is the
|
|
497
|
-
* other editor.
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
* That ordering is the same one `announceCursorFullQuit` documents — what a candidate can
|
|
503
|
-
* act on immediately is what they read first.
|
|
504
|
-
*
|
|
505
|
-
* The offered command carries the token already inlined, for the reason `CursorReentry`
|
|
506
|
-
* gives: a `litmus connect <token>` a candidate has to repair themselves is worse than no
|
|
507
|
-
* command at all.
|
|
508
|
-
*
|
|
509
|
-
* When the editor that answered is Cursor, the quit requirement rides along. It is the
|
|
510
|
-
* headline only, not the whole ENG-1728 notice, because the command being offered prints
|
|
511
|
-
* that notice in full at selection time before anything opens — this is a FORWARD
|
|
512
|
-
* reference to output the candidate is about to see, not ENG-2145's back-reference to
|
|
513
|
-
* output that has scrolled away. Restating the whole block here would also have to
|
|
514
|
-
* restate its "this command is still running and will open Cursor for you", which on
|
|
515
|
-
* this path is false.
|
|
522
|
+
* other editor. Nothing has been opened and no extension installed when this prints.
|
|
523
|
+
*
|
|
524
|
+
* The editor that answered comes first: on a machine where `code` is Cursor, Cursor is
|
|
525
|
+
* almost certainly the daily driver and VS Code may not be installed at all. The PATH
|
|
526
|
+
* fix follows in one line, then the routes that need neither.
|
|
516
527
|
*/
|
|
517
|
-
export function editorMismatchLines(chosen, actual,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
"",
|
|
522
|
-
|
|
523
|
-
` installed. Working in ${actual.label} while you believe you are in ${chosen.label} would`,
|
|
524
|
-
" leave your AI work unrecorded, so this stops here instead.",
|
|
525
|
-
"",
|
|
526
|
-
" Your workspace is up and waiting — nothing has been lost.",
|
|
527
|
-
"",
|
|
528
|
-
` To carry on in ${actual.label} — which is what \`${chosen.bin}\` actually is — run:`,
|
|
529
|
-
"",
|
|
530
|
-
` ${chalk.cyan(`${reentry.command} --ide ${actual.ide}`)}`,
|
|
528
|
+
export function editorMismatchLines(chosen, actual, alias, pageUrl) {
|
|
529
|
+
return [
|
|
530
|
+
chalk.yellow.bold(`⚠ The \`${chosen.bin}\` command on your PATH is ${actual.label}, not ${chosen.label}. Nothing was opened.`),
|
|
531
|
+
` To use ${actual.label}: ${SAME_COMMAND} with ${chalk.cyan(`--ide ${actual.ide}`)}.`,
|
|
532
|
+
` To use ${chosen.label}: in ${chosen.label}, run "${chosen.shellCommand}" from the Command Palette, open a new terminal, then ${SAME_COMMAND} again.`,
|
|
533
|
+
workNowLine(alias, pageUrl),
|
|
531
534
|
];
|
|
532
|
-
if (actual.ide === "cursor") {
|
|
533
|
-
lines.push("");
|
|
534
|
-
lines.push(chalk.yellow(" ⚠ Quit Cursor completely before you run that, or your work is not recorded."));
|
|
535
|
-
lines.push(" That command explains why, in full, before anything opens.");
|
|
536
|
-
}
|
|
537
|
-
lines.push("");
|
|
538
|
-
lines.push(` To carry on in ${chosen.label} instead, put its own \`${chosen.bin}\` command first on`);
|
|
539
|
-
lines.push(` your PATH: in ${chosen.label}, run "${chosen.shellCommand}" from the`);
|
|
540
|
-
lines.push(" Command Palette, then open a NEW terminal and check that");
|
|
541
|
-
lines.push(` \`${chosen.bin} --help\` names ${chosen.label}. Then run the same \`litmus connect\` command again.`);
|
|
542
|
-
lines.push("");
|
|
543
|
-
return lines;
|
|
544
535
|
}
|
|
545
536
|
/**
|
|
546
|
-
* ENG-2332 — the refusal when the candidate's editor is a different build from the one
|
|
547
|
-
* this workspace has
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
* ENG-2867 — but it must say the one thing that DOES change the verdict, and say it first.
|
|
554
|
-
* The image bakes the latest Stable build at image build time and Cursor ships about
|
|
555
|
-
* weekly, so the usual mismatch is a candidate one release behind, and updating is what
|
|
556
|
-
* clears it. The ENG-2332 cut never said so. A candidate who had just been told to quit
|
|
557
|
-
* Cursor read that as the remedy and re-ran the same refused command four times. So the
|
|
558
|
-
* order is: what to do, that retrying without it will not help, the exact command to run
|
|
559
|
-
* after, and only then why. The "why" is one sentence; it used to be the whole notice.
|
|
560
|
-
*
|
|
561
|
-
* Update is not guaranteed to land on the baked build: an image built before the newest
|
|
562
|
-
* release is refused again after updating, since `seedVerdict` wants an exact commit. So
|
|
563
|
-
* the copy says "usually", names that outcome as step 4, and the routes that need no
|
|
564
|
-
* update follow it (Greptile on #3033). Step 4 diagnoses nothing: the check compares
|
|
565
|
-
* commits, and a second refusal has more causes (an older image, a second install found
|
|
566
|
-
* first) than a notice can tell apart. The routes below always work, so step 4 sends the
|
|
567
|
-
* candidate there and states only the one fact the version line proves.
|
|
537
|
+
* ENG-2332 — the refusal when the candidate's editor is a different build from the one
|
|
538
|
+
* this workspace has.
|
|
539
|
+
*
|
|
540
|
+
* ENG-2884: no editor sets `requiresSeedMatch` any more, so this is reachable only if one
|
|
541
|
+
* is measured failing again and the flag goes back on. Kept short for that day: what
|
|
542
|
+
* happened, that updating usually clears it, and the routes that need no update. There is
|
|
543
|
+
* deliberately no "open it anyway".
|
|
568
544
|
*/
|
|
569
|
-
export function editorSeedMismatchLines(ed, verdict,
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
chalk.yellow.bold(` from the one this workspace was built for, so it cannot open it.`),
|
|
576
|
-
"",
|
|
577
|
-
` ${`Your ${ed.label}:`.padEnd(width)}${verdict.clientVersion ?? "unknown version"} (${short(verdict.clientCommit)})`,
|
|
578
|
-
` ${"This workspace:".padEnd(width)}${short(verdict.seedCommit)}`,
|
|
579
|
-
"",
|
|
580
|
-
chalk.bold(` Running this again, or quitting ${ed.label}, will not fix it. Updating usually does:`),
|
|
581
|
-
"",
|
|
582
|
-
` 1. Update ${ed.label}: use "Check for Updates" in ${ed.label}'s menu, or download it from`,
|
|
583
|
-
` ${chalk.cyan(ed.downloadUrl)}`,
|
|
584
|
-
` 2. Quit ${ed.label} completely (Cmd+Q on macOS; File → Exit on Windows/Linux).`,
|
|
585
|
-
` 3. From a terminal that is NOT inside ${ed.label}, run:`,
|
|
586
|
-
` ${chalk.cyan(`${reentry.command} --ide ${ed.ide}`)}`,
|
|
587
|
-
" 4. If it is refused again, use one of the routes below. (If \"Your " + ed.label + "\" still",
|
|
588
|
-
` shows the old version, this command is reading a different ${ed.label} install than`,
|
|
589
|
-
" the one you updated.)",
|
|
590
|
-
"",
|
|
591
|
-
chalk.dim(` (Why: ${ed.label} needs a matching server inside the workspace, and the workspace cannot`),
|
|
592
|
-
chalk.dim(" download one. A mismatched window looks normal, but your work in it is not recorded, so"),
|
|
593
|
-
chalk.dim(" nothing was opened. Your workspace is up and waiting, and nothing is lost.)"),
|
|
594
|
-
"",
|
|
595
|
-
` Refused after updating, or short on time? Work in the same workspace, fully recorded:`,
|
|
596
|
-
"",
|
|
545
|
+
export function editorSeedMismatchLines(ed, verdict, pageUrl, alias) {
|
|
546
|
+
const which = verdict.clientVersion ? `${ed.label} ${verdict.clientVersion}` : `This ${ed.label}`;
|
|
547
|
+
return [
|
|
548
|
+
chalk.yellow.bold(`⚠ ${which} can't open this workspace.`),
|
|
549
|
+
` Try updating ${ed.label} (${chalk.cyan(ed.downloadUrl)}), then ${SAME_COMMAND} again.`,
|
|
550
|
+
workNowLine(alias, pageUrl),
|
|
597
551
|
];
|
|
598
|
-
if (browserIdeUrl) {
|
|
599
|
-
lines.push(` Browser IDE: ${chalk.cyan(browserIdeUrl)}`);
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
lines.push(" Browser IDE: open your assessment page and use the browser IDE there");
|
|
603
|
-
}
|
|
604
|
-
if (ed.ide !== "vscode") {
|
|
605
|
-
lines.push(` VS Code: ${chalk.cyan(`${reentry.command} --ide vscode`)}`);
|
|
606
|
-
}
|
|
607
|
-
lines.push(` Terminal: ${chalk.cyan(`ssh ${alias}`)}`);
|
|
608
|
-
lines.push("");
|
|
609
|
-
return lines;
|
|
610
552
|
}
|
|
611
553
|
/**
|
|
612
554
|
* ENG-2332 — what to say when the editor CLI cannot be found anywhere.
|
|
613
555
|
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
* IDE, which needs nothing installed at all.
|
|
556
|
+
* Never `<bin> --remote …`: that is the binary whose absence is the message. Everything
|
|
557
|
+
* here works without it: the editor's own Remote-SSH picker (the alias is already in
|
|
558
|
+
* their `~/.ssh/config`), the palette command that puts the CLI on PATH, and the routes
|
|
559
|
+
* that need no editor.
|
|
619
560
|
*/
|
|
620
|
-
export function editorNotFoundLines(ed, alias,
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
""
|
|
628
|
-
|
|
629
|
-
" with no command line at all:",
|
|
630
|
-
"",
|
|
631
|
-
` Command Palette → ${chalk.bold("Remote-SSH: Connect to Host…")} → ${chalk.bold(alias)}`,
|
|
632
|
-
"",
|
|
633
|
-
` (Needs the "${ed.remoteSshExt}" extension, which ${ed.label} will offer to install.)`,
|
|
634
|
-
"",
|
|
635
|
-
` To get the \`${ed.bin}\` command for next time: in ${ed.label}, Command Palette →`,
|
|
636
|
-
` "${ed.shellCommand}", then open a NEW terminal.`,
|
|
561
|
+
export function editorNotFoundLines(ed, alias, pageUrl) {
|
|
562
|
+
return [
|
|
563
|
+
chalk.yellow.bold(`⚠ Couldn't find ${ed.label}'s \`${ed.bin}\` command, so nothing was opened.`),
|
|
564
|
+
// Greptile on #3071: this route never restarts Cursor, and Cursor only loads the
|
|
565
|
+
// workspace's prompt recording when the app starts (gotcha 090).
|
|
566
|
+
...(ed.ide === "cursor" ? [" Quit Cursor completely and reopen it first, or your AI prompts won't be recorded."] : []),
|
|
567
|
+
` In ${ed.label}: Command Palette → ${chalk.bold("Remote-SSH: Connect to Host…")} → ${chalk.bold(alias)}`,
|
|
568
|
+
` To get the \`${ed.bin}\` command for next time: Command Palette → "${ed.shellCommand}".`,
|
|
569
|
+
workNowLine(alias, pageUrl),
|
|
637
570
|
];
|
|
638
|
-
if (browserIdeUrl) {
|
|
639
|
-
lines.push("");
|
|
640
|
-
lines.push(" Or skip the editor entirely — the browser IDE needs nothing installed:");
|
|
641
|
-
lines.push(` ${chalk.cyan(browserIdeUrl)}`);
|
|
642
|
-
}
|
|
643
|
-
lines.push("");
|
|
644
|
-
return lines;
|
|
645
571
|
}
|
|
646
572
|
/**
|
|
647
|
-
* ENG-2332 — the
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
* connected from an already-running Cursor has a live, correct, never-loaded hook, and
|
|
656
|
-
* their prompts are not recorded although everything else is. The verification above
|
|
657
|
-
* cannot see that — a server is up either way — so a flat "your work is being recorded"
|
|
658
|
-
* would be the same false reassurance this ticket is about, moved one lane along. The
|
|
659
|
-
* full notice is printed at selection time; this restates only the dependency.
|
|
573
|
+
* ENG-2332 — the footer under the verified success line.
|
|
574
|
+
*
|
|
575
|
+
* ENG-2890: for Cursor, one line naming the window that IS connected, since a restart
|
|
576
|
+
* can leave the candidate's other Cursor windows reopening beside it. The window title
|
|
577
|
+
* is safe to name here and only here: this prints after `verifyEditorAttached` saw a
|
|
578
|
+
* server, unlike the failure branches, where the never-attached window carries the same
|
|
579
|
+
* `[SSH: <alias>]` (ENG-2154). The workspace path is for whoever is debugging, so it is
|
|
580
|
+
* `--verbose` only. VS Code needs no footer.
|
|
660
581
|
*/
|
|
661
|
-
export function editorConnectedLines(ed, alias) {
|
|
662
|
-
const lines = [
|
|
582
|
+
export function editorConnectedLines(ed, alias, verbose = false, platform = process.platform) {
|
|
583
|
+
const lines = [];
|
|
584
|
+
if (verbose)
|
|
585
|
+
lines.push(chalk.dim(` Workspace: ${alias}:${REMOTE_WORKSPACE_DIR}`));
|
|
663
586
|
if (ed.ide === "cursor") {
|
|
664
|
-
|
|
665
|
-
lines.push(
|
|
587
|
+
const mod = platform === "darwin" ? "Cmd" : "Ctrl";
|
|
588
|
+
lines.push(` Use the Cursor window titled "workspace [SSH: ${alias}]". ${mod}+Shift+E shows your files.`);
|
|
666
589
|
}
|
|
667
590
|
return lines;
|
|
668
591
|
}
|
|
669
592
|
/**
|
|
670
|
-
* ENG-2332 —
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
* `
|
|
679
|
-
*
|
|
680
|
-
* first thing worth knowing, and it is already in hand.
|
|
681
|
-
*
|
|
682
|
-
* WHAT IT MAY NOT SAY IS THAT NOTHING WILL CHANGE. That is what it said first, and it is
|
|
683
|
-
* a claim about the future made on a reading that stops at the verification budget. A VS
|
|
684
|
-
* Code client whose build the image did not bake is NOT refused (`requiresSeedMatch`), so
|
|
685
|
-
* its launch legitimately goes on to fetch a server through the mirror — a ~28.6 MB CLI
|
|
686
|
-
* and, on a prewarm miss, the whole bundle — and a slow link can still be fetching when
|
|
687
|
-
* the budget runs out. Telling that candidate their window is dead and pointing them
|
|
688
|
-
* elsewhere is the harm `unverified` exists to avoid, arriving on the branch below it.
|
|
689
|
-
* So it states the bound it actually waited (derived from `VERIFY_SCHEDULE_MS`, never a
|
|
690
|
-
* second hard-coded copy), says the window may yet attach, and offers the other two
|
|
691
|
-
* routes rather than instructing an exit. What it still may not do is tell them to re-run
|
|
692
|
-
* this command, which is the original bug.
|
|
693
|
-
*
|
|
694
|
-
* AND IT MAY NOT HAND THEM A CHECK THAT PASSES ON THE DEAD WINDOW. It pointed at the
|
|
695
|
-
* remote indicator — "the bottom-left corner reads SSH: <alias>" — and this repo has
|
|
696
|
-
* measured that indicator PRESENT in exactly the failure case: `lib/editor-server.ts`'s
|
|
697
|
-
* header records the never-attached window carrying `"remoteAuthority":
|
|
698
|
-
* "ssh-remote+<alias>"` and being indistinguishable from a working one, and
|
|
699
|
-
* `lib/session-end.ts` records (ENG-2154) that a window whose transport is gone still
|
|
700
|
-
* reads `[SSH: <host>]`. A candidate told to look at the corner sees what they were told
|
|
701
|
-
* to look for and goes on working unrecorded, which is this ticket's own bug re-entering
|
|
702
|
-
* through its remediation. The honest sentence is that the two windows look the same —
|
|
703
|
-
* that is WHY the CLI checks — so the copy says so and names no visual test at all.
|
|
593
|
+
* ENG-2332 — a window opened and no server came up in the workspace, so that window is
|
|
594
|
+
* running against the candidate's own machine and records nothing.
|
|
595
|
+
*
|
|
596
|
+
* ENG-2890: four lines. Close the window, retry with the cause that usually explains it
|
|
597
|
+
* (an editor that stopped to ask for a sign-in or an update), or work elsewhere now.
|
|
598
|
+
*
|
|
599
|
+
* IT MAY NOT HAND THEM A CHECK THAT PASSES ON THE DEAD WINDOW. The remote indicator
|
|
600
|
+
* ("SSH: <alias>" in the corner) is PRESENT on the never-attached window
|
|
601
|
+
* (`lib/editor-server.ts`'s header; ENG-2154 in `lib/session-end.ts`), so no visual test
|
|
602
|
+
* is named here at all.
|
|
704
603
|
*/
|
|
705
|
-
export function editorNotConnectedLines(ed, alias,
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
chalk.yellow(" laptop, and it is not recorded."),
|
|
714
|
-
"",
|
|
715
|
-
` We watched your workspace for ${waited} seconds after opening it and nothing`,
|
|
716
|
-
` started there. A first-ever ${ed.label} connection can still be downloading its`,
|
|
717
|
-
" server at that point and attach a little later. There is nothing you can check",
|
|
718
|
-
" by eye: an attached window and an unattached one look exactly the same, which is",
|
|
719
|
-
" why this command checks your workspace instead of asking you to.",
|
|
720
|
-
"",
|
|
604
|
+
export function editorNotConnectedLines(ed, alias, pageUrl, verbose = false) {
|
|
605
|
+
return [
|
|
606
|
+
// Greptile on #3071: a first download of this build's server can outlast the check, so
|
|
607
|
+
// the window may still attach; it is not called dead.
|
|
608
|
+
` It may still be setting up. If the ${ed.label} window doesn't show your workspace files in a couple of minutes, close it: work in it isn't recorded.`,
|
|
609
|
+
` Then ${SAME_COMMAND} again. If ${ed.label} asked you to sign in or update, that's usually why.`,
|
|
610
|
+
workNowLine(alias, pageUrl),
|
|
611
|
+
...detailsLine(verbose),
|
|
721
612
|
];
|
|
722
|
-
if (seed.kind === "mismatch") {
|
|
723
|
-
lines.push(` The likely reason: your ${ed.label} is build ${seed.clientCommit.slice(0, 12)} and this`);
|
|
724
|
-
lines.push(` workspace holds ${seed.seedCommit.slice(0, 12)}. It could not fetch a matching one.`);
|
|
725
|
-
lines.push("");
|
|
726
|
-
}
|
|
727
|
-
if (browserIdeUrl) {
|
|
728
|
-
lines.push(" You can work in the browser IDE instead — same workspace, same files, fully");
|
|
729
|
-
lines.push(" recorded, and nothing to install:");
|
|
730
|
-
lines.push(` ${chalk.cyan(browserIdeUrl)}`);
|
|
731
|
-
}
|
|
732
|
-
else {
|
|
733
|
-
lines.push(" You can work in the browser IDE instead, from your assessment page — same");
|
|
734
|
-
lines.push(" workspace, same files, fully recorded.");
|
|
735
|
-
}
|
|
736
|
-
lines.push("");
|
|
737
|
-
lines.push(" You can also work from a terminal, which does not need the editor at all:");
|
|
738
|
-
lines.push(` ${chalk.cyan(`ssh ${alias}`)}`);
|
|
739
|
-
if (launchLog) {
|
|
740
|
-
lines.push("");
|
|
741
|
-
lines.push(chalk.dim(` ${ed.label} said, while trying to open:`));
|
|
742
|
-
for (const line of launchLog.split(/\r?\n/).slice(-8))
|
|
743
|
-
lines.push(chalk.dim(` ${line}`));
|
|
744
|
-
}
|
|
745
|
-
lines.push("");
|
|
746
|
-
return lines;
|
|
747
613
|
}
|
|
748
614
|
/**
|
|
749
|
-
* ENG-2332 —
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* `
|
|
753
|
-
* running against the candidate's own machine; when `spawn` itself fails that sentence is
|
|
754
|
-
* false, and the reading the verification produces is identical either way (no server,
|
|
755
|
-
* empty launch log). So the spawn error is recorded (`EditorLaunchHandle`) and routed
|
|
756
|
-
* here instead, where the only claim made is the one we can support.
|
|
757
|
-
*
|
|
758
|
-
* It offers the same two routes and, like every other failure branch, offers rather than
|
|
759
|
-
* instructs: there is no window to close, and re-running this command is the remediation
|
|
760
|
-
* the ticket was opened about. The error text is carried verbatim because it is the whole
|
|
761
|
-
* of what we know — `ENOENT` on a resolved path means the app moved or was removed since
|
|
762
|
-
* the `--version` probe a moment earlier, which is a thing the candidate can act on and
|
|
763
|
-
* we cannot diagnose for them.
|
|
615
|
+
* ENG-2332 — the editor never started at all, so no window exists.
|
|
616
|
+
*
|
|
617
|
+
* Its own branch because `editorNotConnectedLines` tells them to close a window, which
|
|
618
|
+
* is false when `spawn` failed. The error is carried verbatim: it is all we know.
|
|
764
619
|
*/
|
|
765
|
-
export function editorLaunchFailedLines(ed, alias,
|
|
766
|
-
|
|
767
|
-
"",
|
|
768
|
-
chalk.yellow.bold(` ⚠ ${ed.label} could not be started, so no window was opened.`),
|
|
769
|
-
"",
|
|
770
|
-
` We found the ${ed.label} command and it answered a moment ago, but running it`,
|
|
771
|
-
" failed:",
|
|
772
|
-
` ${chalk.dim(error)}`,
|
|
773
|
-
"",
|
|
774
|
-
` That usually means ${ed.label} was moved, removed or updated between those two`,
|
|
775
|
-
" moments. Nothing has been opened and nothing is being recorded yet.",
|
|
776
|
-
"",
|
|
777
|
-
];
|
|
778
|
-
if (browserIdeUrl) {
|
|
779
|
-
lines.push(" You can work in the browser IDE instead — same workspace, same files, fully");
|
|
780
|
-
lines.push(" recorded, and nothing to install:");
|
|
781
|
-
lines.push(` ${chalk.cyan(browserIdeUrl)}`);
|
|
782
|
-
}
|
|
783
|
-
else {
|
|
784
|
-
lines.push(" You can work in the browser IDE instead, from your assessment page — same");
|
|
785
|
-
lines.push(" workspace, same files, fully recorded.");
|
|
786
|
-
}
|
|
787
|
-
lines.push("");
|
|
788
|
-
lines.push(" You can also work from a terminal, which does not need the editor at all:");
|
|
789
|
-
lines.push(` ${chalk.cyan(`ssh ${alias}`)}`);
|
|
790
|
-
if (launchLog) {
|
|
791
|
-
lines.push("");
|
|
792
|
-
lines.push(chalk.dim(` ${ed.label} said, while trying to open:`));
|
|
793
|
-
for (const line of launchLog.split(/\r?\n/).slice(-8))
|
|
794
|
-
lines.push(chalk.dim(` ${line}`));
|
|
795
|
-
}
|
|
796
|
-
lines.push("");
|
|
797
|
-
return lines;
|
|
620
|
+
export function editorLaunchFailedLines(ed, alias, pageUrl, error, verbose = false) {
|
|
621
|
+
return [` ${chalk.dim(error)}`, workNowLine(alias, pageUrl), ...detailsLine(verbose)];
|
|
798
622
|
}
|
|
799
623
|
/**
|
|
800
|
-
* ENG-2332 —
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
* — it is bound by the same rule as `editorNotConnectedLines` and broke it the same way.
|
|
806
|
-
*
|
|
807
|
-
* It may NOT tell the candidate to look at the remote indicator. That indicator is
|
|
808
|
-
* rendered from the remote authority, which the never-attached window carries (see
|
|
809
|
-
* `lib/editor-server.ts`'s header, and ENG-2154 in `lib/session-end.ts`), so the check
|
|
810
|
-
* passes in exactly the case it was offered to detect. It states the ambiguity instead
|
|
811
|
-
* and offers the two routes that are not in doubt.
|
|
624
|
+
* ENG-2332 — we could not reach the workspace to ask, which is not "nothing came up".
|
|
625
|
+
*
|
|
626
|
+
* It may NOT offer the remote indicator as a check (ENG-2154: the never-attached window
|
|
627
|
+
* carries it). The file explorer is a fair one: a window not attached to the workspace
|
|
628
|
+
* has no workspace files to list.
|
|
812
629
|
*/
|
|
813
|
-
export function editorUnverifiedLines(ed, alias,
|
|
814
|
-
|
|
815
|
-
`
|
|
816
|
-
"
|
|
817
|
-
|
|
818
|
-
"",
|
|
630
|
+
export function editorUnverifiedLines(ed, alias, pageUrl) {
|
|
631
|
+
return [
|
|
632
|
+
` If the ${ed.label} window shows your workspace files in its file explorer, you're all set.`,
|
|
633
|
+
" If it doesn't, close that window.",
|
|
634
|
+
workNowLine(alias, pageUrl),
|
|
819
635
|
];
|
|
820
|
-
if (browserIdeUrl) {
|
|
821
|
-
lines.push(" You can work in the browser IDE instead — same workspace, same files, fully");
|
|
822
|
-
lines.push(" recorded, and nothing to install:");
|
|
823
|
-
lines.push(` ${chalk.cyan(browserIdeUrl)}`);
|
|
824
|
-
}
|
|
825
|
-
else {
|
|
826
|
-
lines.push(" You can work in the browser IDE instead, from your assessment page — same");
|
|
827
|
-
lines.push(" workspace, same files, fully recorded.");
|
|
828
|
-
}
|
|
829
|
-
lines.push("");
|
|
830
|
-
lines.push(" You can also work from a terminal, which does not need the editor at all:");
|
|
831
|
-
lines.push(` ${chalk.cyan(`ssh ${alias}`)}`);
|
|
832
|
-
return lines;
|
|
833
636
|
}
|
|
834
637
|
/**
|
|
835
638
|
* Ensure the Remote-SSH extension is installed in the user's editor before we
|
|
@@ -976,10 +779,8 @@ export function launchEditor(ed, command, alias, logPath) {
|
|
|
976
779
|
return { launchFailure: () => failure };
|
|
977
780
|
}
|
|
978
781
|
/**
|
|
979
|
-
* Point at the editor's own output
|
|
980
|
-
*
|
|
981
|
-
* `--verbose` was asked for, since `editorLaunchLogPath` gives it a per-run suffix that
|
|
982
|
-
* nobody can guess unaided.
|
|
782
|
+
* Point at the editor's own output, under `--verbose` only (ENG-2890: the failure copy
|
|
783
|
+
* says "Details: add --verbose." instead of printing a path by default).
|
|
983
784
|
*
|
|
984
785
|
* A PATH IS NAMED ONLY WHEN THERE IS SOMETHING AT IT, and the reason is that the file
|
|
985
786
|
* routinely does not exist. `launchEditor` falls back to `stdio: "ignore"` whenever
|
|
@@ -989,15 +790,14 @@ export function launchEditor(ed, command, alias, logPath) {
|
|
|
989
790
|
* Handing a support person a path with nothing behind it reads as the CLI having lost
|
|
990
791
|
* the log, which is a worse answer than saying plainly that the editor wrote nothing.
|
|
991
792
|
*/
|
|
992
|
-
export function editorLaunchOutputLines(logPath, launchLog, verbose
|
|
993
|
-
if (!
|
|
793
|
+
export function editorLaunchOutputLines(logPath, launchLog, verbose) {
|
|
794
|
+
if (!verbose)
|
|
994
795
|
return [];
|
|
995
796
|
if (!launchLog)
|
|
996
797
|
return [chalk.dim(" The editor wrote no output while opening.")];
|
|
997
798
|
const lines = [chalk.dim(` Editor output: ${logPath}`)];
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
lines.push(chalk.dim(` ${line}`));
|
|
799
|
+
for (const line of launchLog.split(/\r?\n/))
|
|
800
|
+
lines.push(chalk.dim(` ${line}`));
|
|
1001
801
|
return lines;
|
|
1002
802
|
}
|
|
1003
803
|
/** What the editor wrote while trying to open, or null when it said nothing. */
|
|
@@ -1023,8 +823,8 @@ function sshProbeRunner(alias) {
|
|
|
1023
823
|
}
|
|
1024
824
|
};
|
|
1025
825
|
}
|
|
1026
|
-
function probeWorkspace(ed, run) {
|
|
1027
|
-
return parseWorkspaceProbe(run(remoteProbeCommand(ed.serverHomeDir, ed.seedCommitPath)));
|
|
826
|
+
function probeWorkspace(ed, run, clientCommit = null) {
|
|
827
|
+
return parseWorkspaceProbe(run(remoteProbeCommand(ed.serverHomeDir, ed.seedCommitPath, clientCommit)));
|
|
1028
828
|
}
|
|
1029
829
|
/**
|
|
1030
830
|
* ENG-2332 — poll the workspace until a server appears, or the budget runs out.
|
|
@@ -1052,7 +852,7 @@ function probeWorkspace(ed, run) {
|
|
|
1052
852
|
* than taken whole.
|
|
1053
853
|
*/
|
|
1054
854
|
const SPAWN_ERROR_POLL_MS = 200;
|
|
1055
|
-
export async function verifyEditorAttached(ed, baseline, run, onWait, launched) {
|
|
855
|
+
export async function verifyEditorAttached(ed, baseline, run, onWait, launched, clientCommit = null) {
|
|
1056
856
|
const started = Date.now();
|
|
1057
857
|
const budget = VERIFY_SCHEDULE_MS[VERIFY_SCHEDULE_MS.length - 1];
|
|
1058
858
|
let sawNotConnected = false;
|
|
@@ -1074,7 +874,7 @@ export async function verifyEditorAttached(ed, baseline, run, onWait, launched)
|
|
|
1074
874
|
if (launched.launchFailure() !== null)
|
|
1075
875
|
return { kind: "unverified" };
|
|
1076
876
|
onWait(Math.round((Date.now() - started) / 1000));
|
|
1077
|
-
const current = probeWorkspace(ed, run);
|
|
877
|
+
const current = probeWorkspace(ed, run, clientCommit);
|
|
1078
878
|
const verdict = verifyVerdict(baseline, current);
|
|
1079
879
|
if (verdict.kind === "connected")
|
|
1080
880
|
return verdict;
|
|
@@ -1112,20 +912,15 @@ export async function attemptEditorLaunch(ed, io) {
|
|
|
1112
912
|
* (piped / CI), so scripted runs never block on the prompt.
|
|
1113
913
|
*
|
|
1114
914
|
* `log` is injectable ONLY so that the Cursor notice below is testable through the real
|
|
1115
|
-
* selection path rather than by reading a string constant
|
|
1116
|
-
*
|
|
1117
|
-
* it was printed is not a test of it.
|
|
1118
|
-
*
|
|
1119
|
-
* `reentry` is the way back in, and it is threaded from `runConnect` rather than
|
|
1120
|
-
* rebuilt here because only the caller holds the token and the resolved base. When it
|
|
1121
|
-
* is absent (a test, a scripted call) the notice degrades to the placeholder form
|
|
1122
|
-
* rather than dropping the route — see `cursorReentryLines`.
|
|
915
|
+
* selection path rather than by reading a string constant, and `platform` so that both
|
|
916
|
+
* of its forms are (see `cursorSelectionNoticeLines`).
|
|
1123
917
|
*/
|
|
1124
|
-
export async function resolveIde(flag, log = console.log,
|
|
918
|
+
export async function resolveIde(flag, log = console.log, platform = process.platform) {
|
|
1125
919
|
const norm = (flag ?? "").trim().toLowerCase();
|
|
1126
920
|
if (norm === "vscode" || norm === "cursor" || norm === "browser" || norm === "ssh") {
|
|
1127
921
|
if (norm === "cursor")
|
|
1128
|
-
|
|
922
|
+
for (const line of cursorSelectionNoticeLines(platform))
|
|
923
|
+
log(line);
|
|
1129
924
|
return { ide: norm, chosen: true };
|
|
1130
925
|
}
|
|
1131
926
|
if (norm)
|
|
@@ -1134,10 +929,10 @@ export async function resolveIde(flag, log = console.log, reentry) {
|
|
|
1134
929
|
return { ide: "vscode", chosen: false };
|
|
1135
930
|
const answered = await promptIdeAnswer();
|
|
1136
931
|
const ide = ideForChoice(answered);
|
|
1137
|
-
// At SELECTION time — not after the ~1-2 minute boot wait,
|
|
1138
|
-
// "opening Cursor" line where it scrolls past. See `announceCursorFullQuit`.
|
|
932
|
+
// At SELECTION time — not after the ~1-2 minute boot wait, where it scrolls past.
|
|
1139
933
|
if (ide === "cursor")
|
|
1140
|
-
|
|
934
|
+
for (const line of cursorSelectionNoticeLines(platform))
|
|
935
|
+
log(line);
|
|
1141
936
|
return { ide, chosen: answered.trim() !== "" };
|
|
1142
937
|
}
|
|
1143
938
|
// How long `litmus connect` waits for a started workspace to become reachable.
|
|
@@ -1185,99 +980,63 @@ export function keepWaitingForWorkspace(elapsedMs, status) {
|
|
|
1185
980
|
export function candidatePageUrl(apiBase, assessmentId) {
|
|
1186
981
|
return `${apiBase}/candidate/${assessmentId}`;
|
|
1187
982
|
}
|
|
1188
|
-
/** The placeholder form, for a call that does not know the token (tests, scripted runs). */
|
|
1189
|
-
const CURSOR_REENTRY_FALLBACK = { command: "litmus connect <token>", pageUrl: null };
|
|
1190
983
|
/**
|
|
1191
|
-
* ENG-
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1198
|
-
*
|
|
1199
|
-
*
|
|
1200
|
-
*
|
|
1201
|
-
* on screen is a quit editor, no running command, and nothing anywhere naming the way
|
|
1202
|
-
* back. On the clock, that reads as the workspace being gone.
|
|
1203
|
-
*
|
|
1204
|
-
* So the route is stated as an instruction rather than as a promise about a process that
|
|
1205
|
-
* may no longer exist, and the terminal it must be run from is named: a candidate who
|
|
1206
|
-
* has just been told to quit an application cannot be told to use that application's
|
|
1207
|
-
* terminal. The assessment page comes second and is the one that survives closing the
|
|
1208
|
-
* tab AND losing the terminal, which is the state Elena was actually in.
|
|
1209
|
-
*
|
|
1210
|
-
* Both routes reopen the SAME workspace — `litmus connect` re-registers this machine's
|
|
1211
|
-
* key and wakes a parked container through the ENG-2005 machinery, and the portal's own
|
|
1212
|
-
* card does the same — so the reassurance is a fact about the platform, not a hope.
|
|
984
|
+
* ENG-1950 — the one thing a Cursor candidate has to be told when they pick Cursor.
|
|
985
|
+
*
|
|
986
|
+
* Cursor reads its prompt-recording setup once, at application startup (ENG-1728,
|
|
987
|
+
* measured), so a Cursor that is already open when the workspace opens records no AI
|
|
988
|
+
* prompts while everything else works. ENG-2890: on macOS connect quits and reopens
|
|
989
|
+
* Cursor itself (`lib/cursor-restart.ts`), so nothing is said here and the restart step
|
|
990
|
+
* speaks when it acts. Elsewhere a graceful quit cannot be confirmed, so the candidate is
|
|
991
|
+
* asked to do it, in two lines: the 23-line notice this replaced was the wall the
|
|
992
|
+
* ENG-2890 candidate read past. The second line exists because quitting Cursor also
|
|
993
|
+
* closes its integrated terminal, and with it this command (ENG-2145).
|
|
1213
994
|
*/
|
|
1214
|
-
export function
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
"",
|
|
1219
|
-
|
|
1220
|
-
"",
|
|
1221
|
-
" That reopens this same workspace, with your work exactly as you left it.",
|
|
995
|
+
export function cursorSelectionNoticeLines(platform = process.platform) {
|
|
996
|
+
if (platformSupportsGracefulCursorRestart(platform))
|
|
997
|
+
return [];
|
|
998
|
+
return [
|
|
999
|
+
" Before Cursor opens, quit it completely (File → Exit), or your AI prompts won't be recorded.",
|
|
1000
|
+
" If this terminal is inside Cursor, run the same litmus connect command from another terminal.",
|
|
1222
1001
|
];
|
|
1223
|
-
// Deliberately NOT "use the button there". The portal's one-click reopen is the
|
|
1224
|
-
// browser-IDE link, and `browser_ide_url` is null wherever `WORKSPACE_IDE_DOMAIN`
|
|
1225
|
-
// is unset — those candidates get the same command in a copy field instead. Naming
|
|
1226
|
-
// a control a candidate then cannot find costs them the trust they need in the rest
|
|
1227
|
-
// of this notice, which is the same line `workspace-guidance.ts` draws over the tool
|
|
1228
|
-
// list. What is true on every deployment is that the page states the route.
|
|
1229
|
-
if (reentry.pageUrl) {
|
|
1230
|
-
lines.push(" Or open your assessment page, which shows the way back in:");
|
|
1231
|
-
lines.push(` ${chalk.cyan(reentry.pageUrl)}`);
|
|
1232
|
-
}
|
|
1233
|
-
else {
|
|
1234
|
-
lines.push(" Your assessment page shows the way back in too.");
|
|
1235
|
-
}
|
|
1236
|
-
return lines;
|
|
1237
1002
|
}
|
|
1238
1003
|
/**
|
|
1239
|
-
* ENG-
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1004
|
+
* ENG-2890 — is this process running in Cursor's integrated terminal?
|
|
1005
|
+
*
|
|
1006
|
+
* Quitting Cursor kills its terminals, so the macOS auto-restart would kill this CLI
|
|
1007
|
+
* between the quit and the relaunch, leaving no editor and no command. `TERM_PROGRAM`
|
|
1008
|
+
* is "vscode" in VS Code AND in Cursor's terminal, so one of Cursor's own markers is
|
|
1009
|
+
* required as well: its trace id, its ToDesktop bundle id, or its bundled askpass node.
|
|
1010
|
+
* VS Code's terminal (bundle id `com.microsoft.VSCode`, no trace id) is not a match.
|
|
1011
|
+
*/
|
|
1012
|
+
export function runningInsideCursorTerminal(env = process.env) {
|
|
1013
|
+
if (env.TERM_PROGRAM !== "vscode")
|
|
1014
|
+
return false;
|
|
1015
|
+
return (Boolean(env.CURSOR_TRACE_ID) ||
|
|
1016
|
+
(env.__CFBundleIdentifier ?? "").includes("todesktop") ||
|
|
1017
|
+
(env.VSCODE_GIT_ASKPASS_NODE ?? "").includes("Cursor"));
|
|
1018
|
+
}
|
|
1019
|
+
/** ENG-2890 — said instead of quitting Cursor out from under this terminal. */
|
|
1020
|
+
export function insideCursorTerminalLines() {
|
|
1021
|
+
return [
|
|
1022
|
+
chalk.yellow.bold("⚠ This terminal is inside Cursor, and restarting Cursor would close it."),
|
|
1023
|
+
" Run the same litmus connect command from Terminal or iTerm instead.",
|
|
1024
|
+
];
|
|
1025
|
+
}
|
|
1026
|
+
/** ENG-2890 — the one line connect prints before its macOS auto-restart of Cursor. */
|
|
1027
|
+
/** Cursor success when this command could not confirm a fresh start (gotcha 090). */
|
|
1028
|
+
export const CURSOR_RECORDING_CAVEAT_LINE = " Your AI prompts are recorded only if Cursor was fully quit before it opened. If it wasn't, quit it and run the same litmus connect command again.";
|
|
1029
|
+
export const CURSOR_RESTARTING_LINE = "Restarting Cursor so your AI prompts are recorded (it reopens on its own)...";
|
|
1030
|
+
/**
|
|
1031
|
+
* ENG-2890 — the quit was asked for and Cursor is still running, so nothing is
|
|
1032
|
+
* launched (see the gate in `connectFlow`). `renderCursorRestartOutcome`'s longer form
|
|
1033
|
+
* stays with init and doctor.
|
|
1262
1034
|
*/
|
|
1263
|
-
export function
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
log(` macOS: ${chalk.bold("Cmd+Q")}`);
|
|
1269
|
-
log(` Windows/Linux: ${chalk.bold("File → Exit")}, or close every Cursor window`);
|
|
1270
|
-
log("");
|
|
1271
|
-
for (const line of cursorReentryLines(reentry))
|
|
1272
|
-
log(line);
|
|
1273
|
-
log("");
|
|
1274
|
-
log(" (If you are running this from a terminal outside Cursor, it is still running");
|
|
1275
|
-
log(" and will open Cursor for you once your workspace is ready.)");
|
|
1276
|
-
log("");
|
|
1277
|
-
log(" Reloading the window or reconnecting is NOT enough — Cursor only picks up your");
|
|
1278
|
-
log(" workspace's recording setup when the app itself starts. If it stays open,");
|
|
1279
|
-
log(" everything will look normal and none of your work will be recorded.");
|
|
1280
|
-
log("");
|
|
1035
|
+
export function cursorQuitUnconfirmedLines() {
|
|
1036
|
+
return [
|
|
1037
|
+
chalk.red.bold("✖ Cursor didn't quit. It may be waiting on a \"Save changes?\" dialog."),
|
|
1038
|
+
" Quit Cursor yourself, then run the same litmus connect command again from Terminal or iTerm.",
|
|
1039
|
+
];
|
|
1281
1040
|
}
|
|
1282
1041
|
/**
|
|
1283
1042
|
* Map a menu answer to an editor. (ENG-1667 item 3; Cursor added by ENG-1950.)
|
|
@@ -1316,7 +1075,7 @@ export function ideMenuLines() {
|
|
|
1316
1075
|
` ${chalk.bold("2")}) VS Code — native, via Remote-SSH`,
|
|
1317
1076
|
// ENG-2389. Cursor sits beside VS Code, where it belongs by kinship, and plain SSH is
|
|
1318
1077
|
// last — see `ideForChoice` for what that renumbering costs and why it is bounded.
|
|
1319
|
-
` ${chalk.bold("3")}) Cursor — native, via Remote-SSH
|
|
1078
|
+
` ${chalk.bold("3")}) Cursor — native, via Remote-SSH`,
|
|
1320
1079
|
` ${chalk.bold("4")}) SSH — just print the command for your terminal`,
|
|
1321
1080
|
];
|
|
1322
1081
|
}
|
|
@@ -1403,24 +1162,33 @@ function promptIdeAnswer() {
|
|
|
1403
1162
|
});
|
|
1404
1163
|
});
|
|
1405
1164
|
}
|
|
1406
|
-
/**
|
|
1165
|
+
/**
|
|
1166
|
+
* Open a URL in the user's default browser. Best-effort, cross-platform. Resolves
|
|
1167
|
+
* whether the opener started, so the caller prints its fallback in order rather than
|
|
1168
|
+
* after the success line, and never prints the URL itself: the browser-IDE URL carries
|
|
1169
|
+
* the `t=` credential (ENG-2890).
|
|
1170
|
+
*/
|
|
1407
1171
|
function openBrowser(url) {
|
|
1408
1172
|
const spec = process.platform === "darwin" ? ["open", [url]]
|
|
1409
1173
|
: process.platform === "win32" ? ["cmd", ["/c", "start", "", url]]
|
|
1410
1174
|
: ["xdg-open", [url]];
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1175
|
+
return new Promise((resolve) => {
|
|
1176
|
+
try {
|
|
1177
|
+
const child = spawn(spec[0], spec[1], { detached: true, stdio: "ignore" });
|
|
1178
|
+
// spawn reports a missing opener ASYNCHRONOUSLY via an 'error' event, not a
|
|
1179
|
+
// sync throw — without this listener an ENOENT (e.g. no xdg-open on a minimal
|
|
1180
|
+
// Linux / WSL / headless box) becomes an uncaughtException that crashes the CLI.
|
|
1181
|
+
child.on("error", () => resolve(false));
|
|
1182
|
+
child.on("spawn", () => resolve(true));
|
|
1183
|
+
child.unref();
|
|
1184
|
+
// Never let the browser opener hold connect up: if neither event arrives, the
|
|
1185
|
+
// launch is assumed to have gone ahead, which is how connect behaved before ENG-2890.
|
|
1186
|
+
setTimeout(() => resolve(true), 3000).unref();
|
|
1187
|
+
}
|
|
1188
|
+
catch {
|
|
1189
|
+
resolve(false);
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1424
1192
|
}
|
|
1425
1193
|
/**
|
|
1426
1194
|
* Drop any stale known_hosts entries for this workspace's target + bastion.
|
|
@@ -1501,14 +1269,10 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1501
1269
|
// Pick the editor up front (flag or interactive prompt) so the choice is made
|
|
1502
1270
|
// before the boot wait, not after.
|
|
1503
1271
|
//
|
|
1504
|
-
// ENG-
|
|
1505
|
-
//
|
|
1506
|
-
|
|
1507
|
-
const
|
|
1508
|
-
command: `litmus connect ${token}`,
|
|
1509
|
-
pageUrl: candidatePageUrl(apiBase, meta.assessmentId),
|
|
1510
|
-
};
|
|
1511
|
-
const { ide: chosenIde, chosen: ideWasChosen } = await resolveIde(ideOpt, console.log, reentry);
|
|
1272
|
+
// ENG-2890 — the assessment page is the one re-entry route connect prints: its URL
|
|
1273
|
+
// carries only the assessment id, never the token.
|
|
1274
|
+
const pageUrl = candidatePageUrl(apiBase, meta.assessmentId);
|
|
1275
|
+
const { ide: chosenIde, chosen: ideWasChosen } = await resolveIde(ideOpt, console.log);
|
|
1512
1276
|
let ide = chosenIde;
|
|
1513
1277
|
// ENG-2814: asked here, next to the editor choice, and uploaded once the workspace is
|
|
1514
1278
|
// ready. Never fails the connect. See `lib/connect-skills.ts`.
|
|
@@ -1578,7 +1342,7 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1578
1342
|
}
|
|
1579
1343
|
boot.succeed("Workspace ready");
|
|
1580
1344
|
if (pendingSkills)
|
|
1581
|
-
skillImport.report = startSkillImport(backendUrl, token, pendingSkills);
|
|
1345
|
+
skillImport.report = startSkillImport(backendUrl, token, pendingSkills, undefined, () => ide);
|
|
1582
1346
|
// Browser IDE: no local SSH / VS Code needed — just open the signed URL.
|
|
1583
1347
|
if (ide === "browser") {
|
|
1584
1348
|
// Greptile on #1713. The browser IDE is now the DEFAULT, so this branch is reached by
|
|
@@ -1596,14 +1360,20 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1596
1360
|
ide = "vscode";
|
|
1597
1361
|
}
|
|
1598
1362
|
else if (fallback === "unavailable") {
|
|
1599
|
-
fatal("The browser IDE isn't available for this workspace yet.", "Open it in native VS Code instead: `litmus connect
|
|
1363
|
+
fatal("The browser IDE isn't available for this workspace yet.", "Open it in native VS Code instead: run the same `litmus connect` command with `--ide vscode`.", { severity: "warning" });
|
|
1600
1364
|
return;
|
|
1601
1365
|
}
|
|
1602
1366
|
}
|
|
1603
1367
|
if (ide === "browser" && conn.browser_ide_url) {
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1368
|
+
// ENG-2890 — the URL carries the `t=` credential, so it is opened, never printed,
|
|
1369
|
+
// except under --verbose for whoever is debugging the opener.
|
|
1370
|
+
// `openBrowser` resolving true means the opener started, not that a browser showed the
|
|
1371
|
+
// page (xdg-open can exit with no browser at all), so the fallback is always given.
|
|
1372
|
+
await openBrowser(conn.browser_ide_url);
|
|
1373
|
+
success("✔ Opening your workspace in the browser");
|
|
1374
|
+
console.log(` If nothing opens, use the browser IDE link on your assessment page: ${chalk.cyan(pageUrl)}`);
|
|
1375
|
+
if (verbose)
|
|
1376
|
+
console.log(chalk.dim(` ${conn.browser_ide_url}`));
|
|
1607
1377
|
return;
|
|
1608
1378
|
}
|
|
1609
1379
|
if (!conn.ssh_command) {
|
|
@@ -1634,7 +1404,8 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1634
1404
|
pruneStaleHostKeys(parsed);
|
|
1635
1405
|
const block = buildSshConfigBlock(alias, KEY_PATH, parsed);
|
|
1636
1406
|
const cfgPath = writeSshConfig(alias, block, buildControlPathBlock(alias, parsed));
|
|
1637
|
-
|
|
1407
|
+
if (verbose)
|
|
1408
|
+
info(`SSH config written to ${cfgPath} (Host "${alias}")`);
|
|
1638
1409
|
// ENG-2693 — prove the connection before handing it off (see `sshPreflight`). The
|
|
1639
1410
|
// client is the one the route will run, as in ENG-2682. A failure stops the route with
|
|
1640
1411
|
// ssh's own words, rather than in an editor that fails the same way silently — and it
|
|
@@ -1745,8 +1516,8 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1745
1516
|
// no-op that touches nothing.
|
|
1746
1517
|
//
|
|
1747
1518
|
// Cursor is still given no laptop-side equivalent of the WRITE: its AI is captured
|
|
1748
|
-
// in-container, which is
|
|
1749
|
-
// protects.
|
|
1519
|
+
// in-container, which is what the Cursor restart (or, off macOS, the quit note at
|
|
1520
|
+
// selection time) protects.
|
|
1750
1521
|
//
|
|
1751
1522
|
// ENG-2152 — a MISMATCH refuses below and opens nothing, so it may not WRITE. See
|
|
1752
1523
|
// `chatConfigActionForIdentity`, which is where that belongs: keying the write on
|
|
@@ -1791,7 +1562,7 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1791
1562
|
// manual-instructions branch. Checking the mismatch first means a Windows candidate
|
|
1792
1563
|
// whose `code` is Cursor is told so, rather than told their editor is not installed.
|
|
1793
1564
|
if (identity.kind === "mismatch") {
|
|
1794
|
-
for (const line of editorMismatchLines(editor, NATIVE_EDITORS[identity.actual],
|
|
1565
|
+
for (const line of editorMismatchLines(editor, NATIVE_EDITORS[identity.actual], alias, pageUrl)) {
|
|
1795
1566
|
console.log(line);
|
|
1796
1567
|
}
|
|
1797
1568
|
return;
|
|
@@ -1800,22 +1571,13 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1800
1571
|
// ENG-2332 — the CLI is not on PATH and not where the installer puts it. Everything
|
|
1801
1572
|
// offered here is reachable WITHOUT it; the line this replaced offered
|
|
1802
1573
|
// `<bin> --remote …`, i.e. the binary whose absence is the message.
|
|
1803
|
-
for (const line of editorNotFoundLines(editor, alias,
|
|
1574
|
+
for (const line of editorNotFoundLines(editor, alias, pageUrl))
|
|
1804
1575
|
console.log(line);
|
|
1805
|
-
//
|
|
1806
|
-
// here
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
// ENG-2145: and so does the way back. This used to point at "the note above", which
|
|
1810
|
-
// is a back-reference to a notice printed BEFORE a boot wait that can run to two
|
|
1811
|
-
// minutes and fills the screen — and, worse, one whose own route back was a promise
|
|
1812
|
-
// about this very command, which by then has returned. So the route is restated
|
|
1813
|
-
// here in full rather than referred to.
|
|
1814
|
-
if (ide === "cursor") {
|
|
1815
|
-
console.log(chalk.yellow(" Quit Cursor completely before you connect, or your work is not recorded."));
|
|
1816
|
-
for (const line of cursorReentryLines(reentry))
|
|
1576
|
+
// A candidate connecting by hand does it from a Cursor they still have to quit, and
|
|
1577
|
+
// nothing here restarts it, so the selection note is restated (empty on macOS).
|
|
1578
|
+
if (ide === "cursor")
|
|
1579
|
+
for (const line of cursorSelectionNoticeLines())
|
|
1817
1580
|
console.log(line);
|
|
1818
|
-
}
|
|
1819
1581
|
return;
|
|
1820
1582
|
}
|
|
1821
1583
|
if (identity.kind === "unverified") {
|
|
@@ -1828,9 +1590,10 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1828
1590
|
// printing this first produced "opening it as you asked" immediately followed by
|
|
1829
1591
|
// "not found on PATH" — two lines contradicting each other about whether anything was
|
|
1830
1592
|
// about to open, which is on the ticket in its own right.
|
|
1831
|
-
|
|
1593
|
+
if (verbose)
|
|
1594
|
+
console.log(chalk.dim(` (Couldn't confirm that \`${editor.bin}\` is ${editor.label}; opening it as you asked.)`));
|
|
1832
1595
|
}
|
|
1833
|
-
if (resolved.source === "install-location") {
|
|
1596
|
+
if (verbose && resolved.source === "install-location") {
|
|
1834
1597
|
info(`Using ${editor.label} at ${resolved.command}`);
|
|
1835
1598
|
console.log(chalk.dim(` (\`${editor.bin}\` isn't on your PATH. ${editor.label}'s "${editor.shellCommand}" adds it.)`));
|
|
1836
1599
|
}
|
|
@@ -1841,9 +1604,9 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1841
1604
|
const probe = sshProbeRunner(alias);
|
|
1842
1605
|
// ENG-2491, Greptile P1 on #2613 — the baseline probe is taken HERE, before
|
|
1843
1606
|
// any decision to quit Cursor, and the resulting `seedVerdict` is checked
|
|
1844
|
-
// before quitting too. `attemptEditorLaunch` below
|
|
1845
|
-
//
|
|
1846
|
-
//
|
|
1607
|
+
// before quitting too. `attemptEditorLaunch` below REFUSES the launch on a
|
|
1608
|
+
// mismatched build for any editor with `requiresSeedMatch` set (none today,
|
|
1609
|
+
// ENG-2884), and a refusal opens nothing — so a quit sequenced
|
|
1847
1610
|
// ahead of that check could close every Cursor window on a path that was
|
|
1848
1611
|
// always going to end in "nothing was opened", with no way given back in
|
|
1849
1612
|
// locally. Checking the identical rule first means a quit only ever
|
|
@@ -1854,20 +1617,22 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1854
1617
|
// two, for the same reason this file already takes it once for the seed
|
|
1855
1618
|
// check and the verification baseline together.
|
|
1856
1619
|
const readSpinner = ora(`Checking your workspace...`).start();
|
|
1857
|
-
const baseline = probeWorkspace(editor, probe);
|
|
1620
|
+
const baseline = probeWorkspace(editor, probe, resolved.version.commit);
|
|
1858
1621
|
readSpinner.stop();
|
|
1859
1622
|
const seed = seedVerdict(resolved.version, baseline);
|
|
1860
1623
|
const seedWillRefuseLaunch = seed.kind === "mismatch" && editor.requiresSeedMatch;
|
|
1861
1624
|
// ENG-2491 — Cursor reads its hooks configuration once, at its OWN launch,
|
|
1862
1625
|
// no matter which remote window it then opens (ENG-1728, measured) — so an
|
|
1863
1626
|
// ALREADY-RUNNING local Cursor never reloads even for a brand new
|
|
1864
|
-
// Remote-SSH connection.
|
|
1865
|
-
// selection time, above) has told the candidate this for months; this acts
|
|
1866
|
-
// on it instead of only saying it, with the same graceful, never-a-kill
|
|
1627
|
+
// Remote-SSH connection. This quits it with the same graceful, never-a-kill
|
|
1867
1628
|
// quit `init` uses (see lib/cursor-restart.ts). The launch a few lines
|
|
1868
1629
|
// below IS the relaunch — connect already opens the editor, so there is
|
|
1869
1630
|
// nothing else to relaunch it into.
|
|
1870
1631
|
//
|
|
1632
|
+
// ENG-2890 — NOT from Cursor's own terminal: the quit would kill this
|
|
1633
|
+
// process before the relaunch, leaving no editor and no command. That case
|
|
1634
|
+
// stops before anything is touched, the same way an unconfirmed quit does.
|
|
1635
|
+
//
|
|
1871
1636
|
// NEVER proceeds to launch on an unconfirmed quit. Doing so would hand the
|
|
1872
1637
|
// new `--remote` argv to the SAME still-running process — Cursor's
|
|
1873
1638
|
// single-instance lock just forwards it as a new window into a hooks
|
|
@@ -1875,23 +1640,30 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1875
1640
|
// failure, not a fix for it. So an unconfirmed quit stops here, the same
|
|
1876
1641
|
// way every other refusal in this file does: nothing opened, workspace
|
|
1877
1642
|
// still up and waiting, and the exact way back in.
|
|
1643
|
+
// Whether the Cursor about to open is a FRESH start, so its prompt recording loads:
|
|
1644
|
+
// it was not running, or this command confirmed it quit. Only then may success say the
|
|
1645
|
+
// work is recorded (Greptile on #3071); elsewhere (Linux, Windows, an unreadable process
|
|
1646
|
+
// list) the candidate was only asked to quit it, and nothing checked that they did.
|
|
1647
|
+
let cursorStartsFresh = false;
|
|
1878
1648
|
if (!seedWillRefuseLaunch && ide === "cursor" && platformSupportsGracefulCursorRestart(process.platform)) {
|
|
1879
1649
|
const cursorIo = realCursorProcessIo();
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1650
|
+
const presence = cursorIo.isRunning();
|
|
1651
|
+
if (presence === "not-running")
|
|
1652
|
+
cursorStartsFresh = true;
|
|
1653
|
+
if (presence === "running") {
|
|
1654
|
+
if (runningInsideCursorTerminal()) {
|
|
1655
|
+
for (const line of insideCursorTerminalLines())
|
|
1656
|
+
console.log(line);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
info(CURSOR_RESTARTING_LINE);
|
|
1883
1660
|
const quit = await attemptQuit(cursorIo);
|
|
1884
1661
|
if (!quit) {
|
|
1885
|
-
for (const line of
|
|
1886
|
-
console.log(line);
|
|
1887
|
-
console.log();
|
|
1888
|
-
console.log(chalk.yellow.bold(" Quit Cursor completely yourself, then run this again:"));
|
|
1889
|
-
for (const line of cursorReentryLines(reentry))
|
|
1662
|
+
for (const line of cursorQuitUnconfirmedLines())
|
|
1890
1663
|
console.log(line);
|
|
1891
|
-
console.log();
|
|
1892
1664
|
return;
|
|
1893
1665
|
}
|
|
1894
|
-
|
|
1666
|
+
cursorStartsFresh = true;
|
|
1895
1667
|
}
|
|
1896
1668
|
}
|
|
1897
1669
|
// A holder rather than a `let`, because the spinner is created inside a callback and
|
|
@@ -1909,12 +1681,16 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1909
1681
|
ensureExtension: () => {
|
|
1910
1682
|
const extSpinner = ora(`Checking ${editor.label} Remote-SSH extension...`).start();
|
|
1911
1683
|
const state = ensureRemoteSshExtension(editor, resolved.command);
|
|
1912
|
-
|
|
1684
|
+
// ENG-2890: an internal step, so it says nothing when it worked.
|
|
1685
|
+
if (state === "failed") {
|
|
1686
|
+
extSpinner.warn(`Couldn't install the Remote-SSH extension (${editor.remoteSshExt}). Install it from ${editor.label}'s Extensions view if ${editor.label} asks.`);
|
|
1687
|
+
}
|
|
1688
|
+
else if (!verbose)
|
|
1689
|
+
extSpinner.stop();
|
|
1690
|
+
else if (state === "present")
|
|
1913
1691
|
extSpinner.succeed("Remote-SSH extension ready");
|
|
1914
|
-
else if (state === "installed")
|
|
1915
|
-
extSpinner.succeed(`Installed the ${editor.label} Remote-SSH extension`);
|
|
1916
1692
|
else
|
|
1917
|
-
extSpinner.
|
|
1693
|
+
extSpinner.succeed(`Installed the ${editor.label} Remote-SSH extension`);
|
|
1918
1694
|
return state;
|
|
1919
1695
|
},
|
|
1920
1696
|
launchLogPath: editorLaunchLogPath,
|
|
@@ -1932,10 +1708,10 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1932
1708
|
if (spinners.verify) {
|
|
1933
1709
|
spinners.verify.text = `Opening ${editor.label} — waiting for it to start in your workspace... (${waited}s)`;
|
|
1934
1710
|
}
|
|
1935
|
-
}, launched),
|
|
1711
|
+
}, launched, resolved.version.commit),
|
|
1936
1712
|
});
|
|
1937
1713
|
if (outcome.kind === "refused") {
|
|
1938
|
-
for (const line of editorSeedMismatchLines(editor, outcome.seed,
|
|
1714
|
+
for (const line of editorSeedMismatchLines(editor, outcome.seed, pageUrl, alias)) {
|
|
1939
1715
|
console.log(line);
|
|
1940
1716
|
}
|
|
1941
1717
|
return;
|
|
@@ -1945,49 +1721,45 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1945
1721
|
// No window was created, so none of the not-connected copy is true here. See
|
|
1946
1722
|
// `editorLaunchFailedLines`.
|
|
1947
1723
|
launchSpinner?.fail(`Couldn't start ${editor.label}.`);
|
|
1948
|
-
for (const line of editorLaunchFailedLines(editor, alias,
|
|
1724
|
+
for (const line of editorLaunchFailedLines(editor, alias, pageUrl, outcome.error, verbose))
|
|
1949
1725
|
console.log(line);
|
|
1950
|
-
|
|
1951
|
-
for (const line of editorLaunchOutputLines(outcome.logPath, outcome.launchLog, verbose, true))
|
|
1726
|
+
for (const line of editorLaunchOutputLines(outcome.logPath, outcome.launchLog, verbose))
|
|
1952
1727
|
console.log(line);
|
|
1953
1728
|
return;
|
|
1954
1729
|
}
|
|
1955
1730
|
if (outcome.verdict.kind === "connected") {
|
|
1956
|
-
|
|
1957
|
-
|
|
1731
|
+
const recordingKnown = editor.ide !== "cursor" || cursorStartsFresh;
|
|
1732
|
+
launchSpinner?.succeed(recordingKnown
|
|
1733
|
+
? `${editor.label} is open in your workspace. Your work is recorded.`
|
|
1734
|
+
: `${editor.label} is open in your workspace.`);
|
|
1735
|
+
if (!recordingKnown)
|
|
1736
|
+
skillImport.footer.push(CURSOR_RECORDING_CAVEAT_LINE);
|
|
1737
|
+
skillImport.footer.push(...editorConnectedLines(editor, alias, verbose));
|
|
1958
1738
|
}
|
|
1959
1739
|
else if (outcome.verdict.kind === "unverified") {
|
|
1960
1740
|
// Three-valued on purpose: we could not reach the workspace to ask, which is not the
|
|
1961
1741
|
// same as knowing nothing came up, and saying either of the other two here would be a
|
|
1962
1742
|
// claim we cannot make. See `verifyVerdict`.
|
|
1963
|
-
launchSpinner?.warn(`Couldn't
|
|
1964
|
-
for (const line of editorUnverifiedLines(editor, alias,
|
|
1743
|
+
launchSpinner?.warn(`Couldn't confirm ${editor.label} connected to your workspace.`);
|
|
1744
|
+
for (const line of editorUnverifiedLines(editor, alias, pageUrl))
|
|
1965
1745
|
console.log(line);
|
|
1966
1746
|
}
|
|
1967
1747
|
else {
|
|
1968
|
-
launchSpinner?.fail(`${editor.label}
|
|
1969
|
-
|
|
1970
|
-
// withheld rather than repeated; without it that tail is the only view of the output.
|
|
1971
|
-
for (const line of editorNotConnectedLines(editor, alias, conn.browser_ide_url ?? null, outcome.seed, verbose ? null : outcome.launchLog)) {
|
|
1748
|
+
launchSpinner?.fail(`${editor.label} hasn't connected to your workspace yet.`);
|
|
1749
|
+
for (const line of editorNotConnectedLines(editor, alias, pageUrl, verbose))
|
|
1972
1750
|
console.log(line);
|
|
1973
|
-
|
|
1974
|
-
|
|
1751
|
+
// Under --verbose: the log path and the editor's whole output. The extension failure
|
|
1752
|
+
// was already warned about when it happened.
|
|
1753
|
+
if (verbose && outcome.extension === "failed") {
|
|
1975
1754
|
console.log(chalk.dim(` The Remote-SSH extension ("${editor.remoteSshExt}") also failed to install, which would explain this.`));
|
|
1976
1755
|
}
|
|
1977
|
-
|
|
1978
|
-
// suffix — so its PATH is named here whether or not --verbose was asked for.
|
|
1979
|
-
for (const line of editorLaunchOutputLines(outcome.logPath, outcome.launchLog, verbose, true))
|
|
1756
|
+
for (const line of editorLaunchOutputLines(outcome.logPath, outcome.launchLog, verbose))
|
|
1980
1757
|
console.log(line);
|
|
1981
1758
|
return;
|
|
1982
1759
|
}
|
|
1983
|
-
//
|
|
1984
|
-
|
|
1985
|
-
// the editor's own resolver message is the only evidence anybody has — and the name
|
|
1986
|
-
// carries a per-run suffix nobody can guess unaided. `editorLaunchOutputLines` still
|
|
1987
|
-
// withholds a path with nothing behind it.
|
|
1988
|
-
for (const line of editorLaunchOutputLines(outcome.logPath, outcome.launchLog, verbose, outcome.verdict.kind !== "connected")) {
|
|
1760
|
+
// Under --verbose, the editor's own output on both remaining branches (ENG-2890).
|
|
1761
|
+
for (const line of editorLaunchOutputLines(outcome.logPath, outcome.launchLog, verbose))
|
|
1989
1762
|
console.log(line);
|
|
1990
|
-
}
|
|
1991
1763
|
// ENG-2154. The window we have just opened outlives the session, and what it
|
|
1992
1764
|
// does when the session ends looks like a failure. This is the only moment
|
|
1993
1765
|
// anything of ours is on the candidate's own machine — `connect` returns here
|
|
@@ -1995,6 +1767,6 @@ async function connectFlow(token, ideOpt, opts, skillImport) {
|
|
|
1995
1767
|
// assessment page — so it is also the only warning that reaches a candidate
|
|
1996
1768
|
// who ends their session somewhere we do not run. See lib/session-end.ts,
|
|
1997
1769
|
// which also carries why we do not simply close the window.
|
|
1998
|
-
skillImport.footer.push(...editorEndingForewarning(
|
|
1770
|
+
skillImport.footer.push(...editorEndingForewarning());
|
|
1999
1771
|
}
|
|
2000
1772
|
//# sourceMappingURL=connect.js.map
|