@songsid/agend 2.1.8-beta.7 → 2.1.8-beta.9

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.
@@ -1674,7 +1674,14 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1674
1674
  /** `/install-cli cancel` — abort the active install and remove its window. */
1675
1675
  cancelInstallSession(): Promise<string>;
1676
1676
  /** `command -v` on a login shell, so PATH additions from rc files count. */
1677
- private verifyBinaryOnLoginShell;
1677
+ /**
1678
+ * The absolute path a fresh login shell resolves `binary` to, or null. Only
1679
+ * a real executable file counts: `command -v` also answers with an alias
1680
+ * definition or a function name, neither of which a spawn could run.
1681
+ */
1682
+ private locateBinaryOnLoginShell;
1683
+ /** Put an installed binary's directory on the fleet's PATH (#1059), once. */
1684
+ private adoptBinaryDirectory;
1678
1685
  /** "Sign in now?" button after a successful install. */
1679
1686
  private handleInstallLoginConfirm;
1680
1687
  /** Discord native `/login` slash — shared by every adapter dispatch block. */
@@ -1,10 +1,10 @@
1
- import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readdirSync, renameSync, copyFileSync, chmodSync, statSync } from "node:fs";
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readdirSync, renameSync, copyFileSync, chmodSync, statSync, accessSync, constants as fsConstants } from "node:fs";
2
2
  import { randomBytes, randomUUID } from "node:crypto";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { freemem, totalmem, cpus } from "node:os";
5
5
  import { createServer } from "node:http";
6
6
  import { Worker } from "node:worker_threads";
7
- import { join, dirname, basename } from "node:path";
7
+ import { join, dirname, basename, delimiter, isAbsolute } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { getAgendHome, ensureWorkspaceGit } from "./paths.js";
10
10
  import { beginFullRestartProgress as persistFullRestartProgress, beginUpdateProgress as persistUpdateProgress, clearUpdateMarker, isUpdateInProgress, readUpdateProgress, setUpdateProgressStage, updateProgressOperation, } from "./update-marker.js";
@@ -7115,7 +7115,7 @@ export class FleetManager {
7115
7115
  * An empty emoji removes that status's override.
7116
7116
  */
7117
7117
  async setPersonaEmoji(instanceName, args) {
7118
- const self = this.fleetConfig?.instances[instanceName];
7118
+ const self = this.ownInstanceConfig(instanceName);
7119
7119
  if (!self)
7120
7120
  return { error: this.personaEmojiMissing(instanceName) };
7121
7121
  const status = (args.status ?? "delivered");
@@ -7160,8 +7160,9 @@ export class FleetManager {
7160
7160
  // Re-read after the validation above, which may have waited on Discord:
7161
7161
  // another set_persona_emoji or a Settings save can have changed another
7162
7162
  // status, or replaced this instance's config object, meanwhile. Change
7163
- // only this one key on what is there now.
7164
- const current = this.fleetConfig?.instances[instanceName];
7163
+ // only this one key on what is there now. Ownership must still hold:
7164
+ // removal during the await must not fall through to an inherited entry.
7165
+ const current = this.ownInstanceConfig(instanceName);
7165
7166
  if (!current)
7166
7167
  return { error: this.personaEmojiMissing(instanceName) };
7167
7168
  const map = { ...(current.status_emojis ?? {}) };
@@ -9688,10 +9689,16 @@ export class FleetManager {
9688
9689
  }
9689
9690
  // The installer may only have added the binary to a profile PATH; a
9690
9691
  // fresh login shell sees that, the fleet process's PATH may not.
9691
- if (!this.verifyBinaryOnLoginShell(info.binary)) {
9692
+ const installedAt = this.locateBinaryOnLoginShell(info.binary);
9693
+ if (!installedAt) {
9692
9694
  await chat.adapter.sendText(chat.chatId, t("install.verify_failed", backend, info.binary), { threadId: chat.threadId }).catch(() => { });
9693
9695
  return;
9694
9696
  }
9697
+ // #1059: make it reachable from this process too. `/login` lists what
9698
+ // `which` finds on the fleet's own PATH, and instances resolve their
9699
+ // binary the same way, so a binary only a login shell could see was
9700
+ // installed, reported as verified, and then offered nowhere.
9701
+ this.adoptBinaryDirectory(installedAt, backend);
9695
9702
  if (!LOGIN_FLOWS[backend]) {
9696
9703
  await chat.adapter.sendText(chat.chatId, t("install.success_no_login", backend), { threadId: chat.threadId }).catch(() => { });
9697
9704
  return;
@@ -9751,15 +9758,35 @@ export class FleetManager {
9751
9758
  return t("install.cancelled", backend);
9752
9759
  }
9753
9760
  /** `command -v` on a login shell, so PATH additions from rc files count. */
9754
- verifyBinaryOnLoginShell(binary) {
9761
+ /**
9762
+ * The absolute path a fresh login shell resolves `binary` to, or null. Only
9763
+ * a real executable file counts: `command -v` also answers with an alias
9764
+ * definition or a function name, neither of which a spawn could run.
9765
+ */
9766
+ locateBinaryOnLoginShell(binary) {
9755
9767
  try {
9756
- const result = spawnSync("bash", ["-lc", `command -v ${binary}`], { timeout: 10_000, stdio: "pipe" });
9757
- return result.status === 0;
9768
+ const result = spawnSync("bash", ["-lc", `command -v ${binary}`], { timeout: 10_000, stdio: "pipe", encoding: "utf8" });
9769
+ if (result.status !== 0)
9770
+ return null;
9771
+ const path = String(result.stdout ?? "").trim().split("\n").pop()?.trim() ?? "";
9772
+ if (!isAbsolute(path))
9773
+ return null;
9774
+ accessSync(path, fsConstants.X_OK);
9775
+ return statSync(path).isFile() ? path : null;
9758
9776
  }
9759
9777
  catch {
9760
- return false;
9778
+ return null;
9761
9779
  }
9762
9780
  }
9781
+ /** Put an installed binary's directory on the fleet's PATH (#1059), once. */
9782
+ adoptBinaryDirectory(binaryPath, backend) {
9783
+ const dir = dirname(binaryPath);
9784
+ const entries = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
9785
+ if (entries.includes(dir))
9786
+ return;
9787
+ process.env.PATH = [dir, ...entries].join(delimiter);
9788
+ this.logger.info({ backend, dir }, "Added the installed CLI's directory to the fleet PATH");
9789
+ }
9763
9790
  /** "Sign in now?" button after a successful install. */
9764
9791
  async handleInstallLoginConfirm(data, callbackAdapterId, receivingAdapter) {
9765
9792
  const claimed = this.consumeNonceCallback(INSTALL_LOGIN_CALLBACK_PREFIX, /^install-login:([0-9a-f]+):(go|later)$/, data, callbackAdapterId, receivingAdapter);