@songsid/agend 2.3.4-beta.3 → 2.3.4-beta.4

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.
Files changed (71) hide show
  1. package/dist/backend/claude-code.js +1 -1
  2. package/dist/backend/claude-code.js.map +1 -1
  3. package/dist/backend/codex-session-lookup.d.ts +35 -0
  4. package/dist/backend/codex-session-lookup.js +93 -0
  5. package/dist/backend/codex-session-lookup.js.map +1 -1
  6. package/dist/backend/codex.d.ts +27 -0
  7. package/dist/backend/codex.js +116 -20
  8. package/dist/backend/codex.js.map +1 -1
  9. package/dist/daemon.d.ts +9 -0
  10. package/dist/daemon.js +144 -44
  11. package/dist/daemon.js.map +1 -1
  12. package/dist/dialog-screen-text.d.ts +16 -0
  13. package/dist/dialog-screen-text.js +51 -0
  14. package/dist/dialog-screen-text.js.map +1 -0
  15. package/dist/fleet-manager.d.ts +4 -0
  16. package/dist/fleet-manager.js +46 -12
  17. package/dist/fleet-manager.js.map +1 -1
  18. package/dist/install-stream.d.ts +53 -0
  19. package/dist/install-stream.js +134 -0
  20. package/dist/install-stream.js.map +1 -0
  21. package/dist/instance-lifecycle.d.ts +12 -0
  22. package/dist/instance-lifecycle.js +31 -2
  23. package/dist/instance-lifecycle.js.map +1 -1
  24. package/dist/locale.js +26 -6
  25. package/dist/locale.js.map +1 -1
  26. package/dist/log-service.d.ts +2 -0
  27. package/dist/log-service.js +7 -3
  28. package/dist/log-service.js.map +1 -1
  29. package/dist/login-manager.d.ts +6 -0
  30. package/dist/login-manager.js +10 -1
  31. package/dist/login-manager.js.map +1 -1
  32. package/dist/outbound-handlers.js +56 -0
  33. package/dist/outbound-handlers.js.map +1 -1
  34. package/dist/outbound-schemas.d.ts +1 -0
  35. package/dist/outbound-schemas.js +3 -0
  36. package/dist/outbound-schemas.js.map +1 -1
  37. package/dist/selection-card.d.ts +2 -3
  38. package/dist/selection-card.js +99 -26
  39. package/dist/selection-card.js.map +1 -1
  40. package/dist/tmux-manager.d.ts +6 -0
  41. package/dist/tmux-manager.js +17 -0
  42. package/dist/tmux-manager.js.map +1 -1
  43. package/dist/ui/chat-store.js +20 -3
  44. package/dist/ui/fleet-canvas.js +4 -3
  45. package/dist/ui/fleet-office-model.js +55 -22
  46. package/dist/ui/fleet-office-overview.js +6 -38
  47. package/dist/ui/fleet-office-skin.js +12 -4
  48. package/dist/ui/fleet-office-store.js +5 -0
  49. package/dist/ui/fleet-office-zones.js +2 -2
  50. package/dist/ui/fleet-office.js +31 -1
  51. package/dist/ui/panel-log.js +7 -1
  52. package/dist/ui/shared/app-i18n.js +2 -2
  53. package/dist/ui/shared/app-shell.js +4 -2
  54. package/dist/ui/shared/app-stream.js +26 -0
  55. package/dist/ui/shared/app.css +21 -1
  56. package/dist/ui/shared/panel-view.js +185 -10
  57. package/dist/ui/shared/view-strings.js +23 -0
  58. package/dist/unattended-install.d.ts +6 -0
  59. package/dist/unattended-install.js +65 -0
  60. package/dist/unattended-install.js.map +1 -0
  61. package/dist/view-api.d.ts +15 -0
  62. package/dist/view-api.js +94 -33
  63. package/dist/view-api.js.map +1 -1
  64. package/dist/web-api.js +7 -2
  65. package/dist/web-api.js.map +1 -1
  66. package/dist/web-chat-history.d.ts +20 -0
  67. package/dist/web-chat-history.js +21 -0
  68. package/dist/web-chat-history.js.map +1 -1
  69. package/dist/web-guest-access.js +5 -1
  70. package/dist/web-guest-access.js.map +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,16 @@
1
+ /**
2
+ * M-2269: what a detected dialog or prompt looked like, for the web Log. Claude Code never writes its permission
3
+ * dialogs, "Resume this conversation?" or limit banners to the transcript, and an alt-screen CLI leaves no tmux
4
+ * scrollback: the capture the scan already took is the only record of them. Read-only bookkeeping — taken from that
5
+ * capture, never a new one.
6
+ *
7
+ * The bottom of the screen (where a TUI draws its dialog), ANSI stripped, secrets masked, at most SCREEN_MAX_LINES
8
+ * lines and SCREEN_MAX_BYTES UTF-8 bytes. Empty when there is nothing to show.
9
+ */
10
+ export declare const SCREEN_MAX_LINES = 30;
11
+ export declare const SCREEN_MAX_BYTES = 2048;
12
+ /** M-2281: the same cleaning with other bounds (the live install view keeps fewer lines, under a chat message's size). */
13
+ export declare function dialogScreenText(pane: string | null | undefined, limits?: {
14
+ lines: number;
15
+ bytes: number;
16
+ }): string;
@@ -0,0 +1,51 @@
1
+ import { maskSecrets } from "./log-steps.js";
2
+ /**
3
+ * M-2269: what a detected dialog or prompt looked like, for the web Log. Claude Code never writes its permission
4
+ * dialogs, "Resume this conversation?" or limit banners to the transcript, and an alt-screen CLI leaves no tmux
5
+ * scrollback: the capture the scan already took is the only record of them. Read-only bookkeeping — taken from that
6
+ * capture, never a new one.
7
+ *
8
+ * The bottom of the screen (where a TUI draws its dialog), ANSI stripped, secrets masked, at most SCREEN_MAX_LINES
9
+ * lines and SCREEN_MAX_BYTES UTF-8 bytes. Empty when there is nothing to show.
10
+ */
11
+ export const SCREEN_MAX_LINES = 30;
12
+ export const SCREEN_MAX_BYTES = 2048;
13
+ // CSI / OSC / other ESC sequences, and the C0 controls that are not line structure.
14
+ const ANSI = /\u001b(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007\u001b]*(?:\u0007|\u001b\\)|[@-Z\\-_])/g;
15
+ const CONTROLS = /[\u0000-\u0008\u000b-\u001f\u007f]/g;
16
+ /**
17
+ * A line that is only base64 (16+ characters, PEM wraps at 64 — or shorter when rewrapped): the body of a key or
18
+ * certificate whose BEGIN line may not even be on screen (scrolled off the top of the capture itself), so the
19
+ * whole-capture mask above cannot see it. A dialog never needs such a line; it is masked whatever it is.
20
+ */
21
+ const KEY_BODY = /^(\s*)[A-Za-z0-9+/]{16,}={0,2}\s*$/;
22
+ function maskKeyBody(line) {
23
+ const m = KEY_BODY.exec(line);
24
+ return m && /[0-9+/]/.test(line) && /[A-Za-z]/.test(line) ? `${m[1]}[REDACTED]` : line;
25
+ }
26
+ /** M-2281: the same cleaning with other bounds (the live install view keeps fewer lines, under a chat message's size). */
27
+ export function dialogScreenText(pane, limits = { lines: SCREEN_MAX_LINES, bytes: SCREEN_MAX_BYTES }) {
28
+ if (!pane)
29
+ return "";
30
+ const stripped = pane.replace(ANSI, "").replace(/\r\n?/g, "\n").replace(CONTROLS, "");
31
+ // #2967 review: mask the WHOLE capture before keeping its bottom — a private key whose BEGIN line sits above the cut
32
+ // would otherwise leave its body and END below it, where no rule can tell them apart from text any more.
33
+ const lines = maskSecrets(stripped).split("\n").map(l => maskKeyBody(l.replace(/\s+$/, "")));
34
+ while (lines.length && lines[lines.length - 1] === "")
35
+ lines.pop();
36
+ const tail = lines.slice(-limits.lines);
37
+ while (tail.length && tail[0] === "")
38
+ tail.shift();
39
+ let text = tail.join("\n");
40
+ // The byte cap keeps the bottom (the dialog and its options), cut at a whole line, then a whole character.
41
+ while (Buffer.byteLength(text, "utf8") > limits.bytes && text.includes("\n"))
42
+ text = text.slice(text.indexOf("\n") + 1);
43
+ if (Buffer.byteLength(text, "utf8") > limits.bytes) {
44
+ const chars = [...text];
45
+ while (chars.length && Buffer.byteLength(chars.join(""), "utf8") > limits.bytes)
46
+ chars.shift();
47
+ text = chars.join("");
48
+ }
49
+ return text;
50
+ }
51
+ //# sourceMappingURL=dialog-screen-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog-screen-text.js","sourceRoot":"","sources":["../src/dialog-screen-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,oFAAoF;AACpF,MAAM,IAAI,GAAG,gFAAgF,CAAC;AAC9F,MAAM,QAAQ,GAAG,qCAAqC,CAAC;AAEvD;;;;GAIG;AACH,MAAM,QAAQ,GAAG,oCAAoC,CAAC;AACtD,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;AACzF,CAAC;AAED,0HAA0H;AAC1H,MAAM,UAAU,gBAAgB,CAAC,IAA+B,EAAE,MAAM,GAAqC,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,EAAE;IAC/J,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACtF,qHAAqH;IACrH,yGAAyG;IACzG,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7F,OAAO,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,GAAG,EAAE,CAAC;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IACnD,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,2GAA2G;IAC3G,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACxH,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QACxB,OAAO,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK;YAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/F,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -3639,6 +3639,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
3639
3639
  /** "Resend token" button (web login): only the requester may press it; the token never enters the channel. */
3640
3640
  private handleLoginTokenResend;
3641
3641
  private activeInstall;
3642
+ /** M-2281: how long `/login`'s install window may run (a field so a smoke can shorten it). */
3643
+ installTimeoutMs: number;
3642
3644
  /** An install that succeeded, between its success line and the sign-in it hands over to (#1131). */
3643
3645
  private installHandoff;
3644
3646
  /** Start a CLI install session. Caller enforces admin. */
@@ -3653,6 +3655,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
3653
3655
  }): Promise<string>;
3654
3656
  /** Whether this backend's CLI is on the fleet's PATH (what `/login` installs when it is not). */
3655
3657
  isCliInstalled(backend: string): boolean;
3658
+ /** M-2281: isCliInstalled without holding the fleet loop (a chat click's path: `which` on a slow PATH can take seconds). */
3659
+ isCliInstalledOffLoop(backend: string): Promise<boolean>;
3656
3660
  /** Abort the active install (`/login cancel`) and remove its window. */
3657
3661
  cancelInstallSession(): Promise<string>;
3658
3662
  /** `command -v` on a login shell, so PATH additions from rc files count. */
@@ -27,6 +27,8 @@ import { clearPublicLinkOwner, readPublicLinkOwner, writePublicLinkOwner } from
27
27
  import { PUBLIC_PREVIEW_FRAME_SRC, PUBLIC_PREVIEW_TUNNEL_DIR, PublicPreview, publicPreviewWanted } from "./public-preview.js";
28
28
  import { CloudflaredProvider } from "./tunnel/cloudflared.js";
29
29
  import { ensureCloudflared } from "./tunnel/cloudflared-install.js";
30
+ import { unattendedInstallCommand } from "./unattended-install.js";
31
+ import { InstallStream } from "./install-stream.js";
30
32
  import { namedTestLane, runNamedLinkTest } from "./tunnel/named-link-test.js";
31
33
  import { daemonTopicMode, isWebOnlyFleetConfig } from "./web-only-fleet.js";
32
34
  import { OFFICE_SHARE_TUNNEL_DIR, OfficeShareService } from "./office-share-service.js";
@@ -107,7 +109,7 @@ import { WebhookEmitter } from "./webhook-emitter.js";
107
109
  import { TmuxControlClient } from "./tmux-control.js";
108
110
  import { safeHandler } from "./safe-async.js";
109
111
  import { RoutingEngine } from "./routing-engine.js";
110
- import { InstanceLifecycle, SupersededStartError, BACKEND_INSTALLATION_INFO, checkBinaryInstalled, } from "./instance-lifecycle.js";
112
+ import { InstanceLifecycle, SupersededStartError, BACKEND_INSTALLATION_INFO, checkBinaryInstalled, checkBinaryInstalledAsync, } from "./instance-lifecycle.js";
111
113
  import { TopicArchiver } from "./topic-archiver.js";
112
114
  import { StatuslineWatcher } from "./statusline-watcher.js";
113
115
  import { applyInstanceConfigPatch, outboundHandlers } from "./outbound-handlers.js";
@@ -444,6 +446,8 @@ const CANCEL_BTN_LEDGER_FILE = "cancel-buttons.json";
444
446
  const PROGRESS_UPDATE_INTERVAL_MS = 60_000;
445
447
  /** Floor between tool-progress-driven bubble edits (Telegram flood safety). */
446
448
  const TOOL_PROGRESS_EDIT_MIN_MS = 4_000;
449
+ /** M-2281: how long `/login`'s install window may run before it is stopped. */
450
+ const INSTALL_TIMEOUT_MS = 10 * 60 * 1000;
447
451
  /** Elapsed time is only shown once work has clearly outlasted a quick answer. */
448
452
  /**
449
453
  * Default delay before the button starts showing elapsed time. Configurable via
@@ -17713,7 +17717,8 @@ export class FleetManager {
17713
17717
  return admitted.refusal;
17714
17718
  opts = { ...opts, profile: admitted.profile };
17715
17719
  }
17716
- if (BACKEND_INSTALLATION_INFO[wanted] && !this.isCliInstalled(wanted)) {
17720
+ // M-2281: asked off the fleet loop — this runs on a chat click.
17721
+ if (BACKEND_INSTALLATION_INFO[wanted] && !(await this.isCliInstalledOffLoop(wanted))) {
17717
17722
  const flow = LOGIN_FLOWS[wanted];
17718
17723
  this.recordLoginFlow(wanted, flow && flow.remoteLogin !== "unsupported" ? "install_then_login" : "install_only", chat.userId);
17719
17724
  return opts.profile !== undefined
@@ -17938,6 +17943,8 @@ export class FleetManager {
17938
17943
  // installer, judge by exit code, verify the binary on a fresh login shell,
17939
17944
  // then sign in (the hand-off below).
17940
17945
  activeInstall = null;
17946
+ /** M-2281: how long `/login`'s install window may run (a field so a smoke can shorten it). */
17947
+ installTimeoutMs = INSTALL_TIMEOUT_MS;
17941
17948
  /** An install that succeeded, between its success line and the sign-in it hands over to (#1131). */
17942
17949
  installHandoff = null;
17943
17950
  /** Start a CLI install session. Caller enforces admin. */
@@ -17946,11 +17953,13 @@ export class FleetManager {
17946
17953
  const info = BACKEND_INSTALLATION_INFO[backend];
17947
17954
  if (!info)
17948
17955
  return t("install.unsupported", backendArg);
17949
- if (checkBinaryInstalled(info.binary))
17950
- return t("install.already", backend, info.binary);
17951
17956
  // Reserve the fleet-wide window before the first await (shared with web
17952
17957
  // login). Owned by this method until the session is published.
17953
17958
  const claim = this.loginWindow.tryClaim("install", backend);
17959
+ if (claim && await checkBinaryInstalledAsync(info.binary)) {
17960
+ this.loginWindow.release(claim);
17961
+ return t("install.already", backend, info.binary);
17962
+ }
17954
17963
  if (!claim)
17955
17964
  return this.loginWindow.busyMessage();
17956
17965
  let tmux;
@@ -17970,16 +17979,39 @@ export class FleetManager {
17970
17979
  // A synthetic flow: success is decided by the installer's exit code
17971
17980
  // (LoginSession treats a clean exit as success), never by pane text. The
17972
17981
  // session only judges that verdict; it does not read the output for URLs or prompts.
17982
+ // M-2281 (#2980): no terminal to ask on, low priority, stoppable as a whole, the user's own $SHELL — see
17983
+ // unattended-install.ts. A prompt nobody can answer fails at once instead of waiting out the timeout.
17973
17984
  const flow = {
17974
17985
  backend,
17975
- command: info.unattended ?? info.install,
17986
+ command: unattendedInstallCommand(info.unattended ?? info.install),
17976
17987
  successPattern: /$^/,
17977
- timeoutMs: 10 * 60 * 1000,
17988
+ timeoutMs: this.installTimeoutMs,
17978
17989
  };
17990
+ // M-2281: the installer's output, live, in the chat that asked — no page shows this window.
17991
+ // Telegram gets its own <pre> (its markdown conversion keeps a fence as literal backticks); others a fenced block.
17992
+ const frameStyle = chat.adapter.type === "telegram" ? "html" : "markdown";
17993
+ const stream = new InstallStream({
17994
+ send: text => chat.adapter.sendText(chat.chatId, text, { threadId: chat.threadId, allowedMentions: { parse: [] } }),
17995
+ edit: (messageId, text) => chat.adapter.editMessage(chat.chatId, messageId, text, chat.threadId, { format: frameStyle }),
17996
+ warn: (message, err) => this.logger.debug({ backend, err: err?.message ?? String(err) }, message),
17997
+ }, t("install.stream_running", backend), undefined, undefined, frameStyle);
17998
+ const minutes = String(Math.max(1, Math.round(this.installTimeoutMs / 60_000)));
17979
17999
  const session = new LoginSession(flow, tmux, {
18000
+ onProgress: pane => stream.update(pane),
17980
18001
  onDone: async ({ ok, detail, cleanupFailed }) => {
17981
18002
  this.activeInstall = null;
17982
18003
  this.loginWindow.release(claim);
18004
+ // Between the install ending and the sign-in starting there is no
18005
+ // session to cancel; this hand-off is what `/login cancel` stops — armed
18006
+ // before the first await here, so a cancel during any of them counts.
18007
+ const handoff = ok ? { backend, cancelled: false } : null;
18008
+ if (handoff)
18009
+ this.installHandoff = handoff;
18010
+ // The live view's last frame first, so the result line below follows the output it is about.
18011
+ await stream.finish(ok ? t("install.stream_ok", backend)
18012
+ : detail === "timeout" ? t("install.stream_timeout", backend, minutes)
18013
+ : detail === "cancelled" ? t("install.stream_cancelled", backend)
18014
+ : t("install.stream_failed", backend)).catch(() => { });
17983
18015
  if (cleanupFailed) {
17984
18016
  await chat.adapter.sendText(chat.chatId, t("login.web_cleanup_failed", backend), { threadId: chat.threadId }).catch(() => { });
17985
18017
  }
@@ -17987,15 +18019,12 @@ export class FleetManager {
17987
18019
  // A cancel is user-initiated — the cancel command's own reply already
17988
18020
  // said so; a second message here would be a duplicate.
17989
18021
  if (detail !== "cancelled") {
17990
- await chat.adapter.sendText(chat.chatId, t("install.failed", backend, detail), { threadId: chat.threadId }).catch(() => { });
18022
+ await chat.adapter.sendText(chat.chatId, detail === "timeout" ? t("install.timeout", backend, minutes) : t("install.failed", backend, detail), { threadId: chat.threadId }).catch(() => { });
17991
18023
  }
17992
18024
  return;
17993
18025
  }
17994
- // Between the install ending and the sign-in starting there is no
17995
- // session to cancel; this hand-off is what `/login cancel` stops — from
17996
- // here on, as the login-shell lookup below is an await.
17997
- const handoff = { backend, cancelled: false };
17998
- this.installHandoff = handoff;
18026
+ if (!handoff || handoff.cancelled || this.installHandoff !== handoff)
18027
+ return;
17999
18028
  // The installer may only have added the binary to a profile PATH; a
18000
18029
  // fresh login shell sees that, the fleet process's PATH may not.
18001
18030
  const installedAt = (await this.locateBinaryOnLoginShell(info.binary)) ?? this.locateInInstallerBinDirs(info);
@@ -18068,6 +18097,11 @@ export class FleetManager {
18068
18097
  const info = BACKEND_INSTALLATION_INFO[backend];
18069
18098
  return !!info && checkBinaryInstalled(info.binary);
18070
18099
  }
18100
+ /** M-2281: isCliInstalled without holding the fleet loop (a chat click's path: `which` on a slow PATH can take seconds). */
18101
+ async isCliInstalledOffLoop(backend) {
18102
+ const info = BACKEND_INSTALLATION_INFO[backend];
18103
+ return !!info && await checkBinaryInstalledAsync(info.binary);
18104
+ }
18071
18105
  /** Abort the active install (`/login cancel`) and remove its window. */
18072
18106
  async cancelInstallSession() {
18073
18107
  if (!this.activeInstall)