@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.
- package/dist/backend/claude-code.js +1 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-session-lookup.d.ts +35 -0
- package/dist/backend/codex-session-lookup.js +93 -0
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +27 -0
- package/dist/backend/codex.js +116 -20
- package/dist/backend/codex.js.map +1 -1
- package/dist/daemon.d.ts +9 -0
- package/dist/daemon.js +144 -44
- package/dist/daemon.js.map +1 -1
- package/dist/dialog-screen-text.d.ts +16 -0
- package/dist/dialog-screen-text.js +51 -0
- package/dist/dialog-screen-text.js.map +1 -0
- package/dist/fleet-manager.d.ts +4 -0
- package/dist/fleet-manager.js +46 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/install-stream.d.ts +53 -0
- package/dist/install-stream.js +134 -0
- package/dist/install-stream.js.map +1 -0
- package/dist/instance-lifecycle.d.ts +12 -0
- package/dist/instance-lifecycle.js +31 -2
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +26 -6
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +2 -0
- package/dist/log-service.js +7 -3
- package/dist/log-service.js.map +1 -1
- package/dist/login-manager.d.ts +6 -0
- package/dist/login-manager.js +10 -1
- package/dist/login-manager.js.map +1 -1
- package/dist/outbound-handlers.js +56 -0
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +1 -0
- package/dist/outbound-schemas.js +3 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/selection-card.d.ts +2 -3
- package/dist/selection-card.js +99 -26
- package/dist/selection-card.js.map +1 -1
- package/dist/tmux-manager.d.ts +6 -0
- package/dist/tmux-manager.js +17 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/ui/chat-store.js +20 -3
- package/dist/ui/fleet-canvas.js +4 -3
- package/dist/ui/fleet-office-model.js +55 -22
- package/dist/ui/fleet-office-overview.js +6 -38
- package/dist/ui/fleet-office-skin.js +12 -4
- package/dist/ui/fleet-office-store.js +5 -0
- package/dist/ui/fleet-office-zones.js +2 -2
- package/dist/ui/fleet-office.js +31 -1
- package/dist/ui/panel-log.js +7 -1
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-shell.js +4 -2
- package/dist/ui/shared/app-stream.js +26 -0
- package/dist/ui/shared/app.css +21 -1
- package/dist/ui/shared/panel-view.js +185 -10
- package/dist/ui/shared/view-strings.js +23 -0
- package/dist/unattended-install.d.ts +6 -0
- package/dist/unattended-install.js +65 -0
- package/dist/unattended-install.js.map +1 -0
- package/dist/view-api.d.ts +15 -0
- package/dist/view-api.js +94 -33
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.js +7 -2
- package/dist/web-api.js.map +1 -1
- package/dist/web-chat-history.d.ts +20 -0
- package/dist/web-chat-history.js +21 -0
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-guest-access.js +5 -1
- package/dist/web-guest-access.js.map +1 -1
- 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"}
|
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -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. */
|
package/dist/fleet-manager.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
17995
|
-
|
|
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)
|