@songsid/agend 2.1.4-beta.47 → 2.1.4-beta.48

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.
@@ -10,7 +10,7 @@ export interface InstanceStatusData {
10
10
  costCents: number;
11
11
  }
12
12
  export interface AlertData {
13
- type: "hang" | "cost_warn" | "cost_limit" | "schedule_deferred" | "rotation" | "cancel" | "interactive_prompt" | "exit_restart" | "clear_confirm" | "tip" | "login";
13
+ type: "hang" | "cost_warn" | "cost_limit" | "schedule_deferred" | "rotation" | "cancel" | "interactive_prompt" | "exit_restart" | "clear_confirm" | "tip" | "login" | "install";
14
14
  instanceName: string;
15
15
  message: string;
16
16
  choices?: Choice[];
@@ -93,6 +93,13 @@ export interface FleetContext {
93
93
  promptTip?(generalName: string, adapter: import("./channel/types.js").ChannelAdapter, chatId: string, threadId?: string): Promise<"posted" | "empty" | "unavailable">;
94
94
  /** Persistently enable advanced tips without waiting for the dismissal threshold. */
95
95
  unlockAdvancedTips?(userId: string): boolean;
96
+ /** `/install-cli` backend chooser buttons. Caller enforces fleet-admin. */
97
+ promptInstallBackends?(chat: {
98
+ adapter: import("./channel/types.js").ChannelAdapter;
99
+ adapterId: string;
100
+ chatId: string;
101
+ threadId?: string;
102
+ }): Promise<void>;
96
103
  /** `/login` backend chooser buttons. Caller enforces fleet-admin. */
97
104
  promptLoginBackends?(chat: {
98
105
  adapter: import("./channel/types.js").ChannelAdapter;
@@ -832,6 +832,28 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
832
832
  chatId: string;
833
833
  threadId?: string;
834
834
  }): Promise<void>;
835
+ /**
836
+ * Backend chooser for a bare `/install-cli`, mirroring promptLoginBackends so
837
+ * both commands feel the same. Built on postNonceButtonPrompt rather than the
838
+ * `/model` selection coordinator: that is the mechanism `/login` already uses,
839
+ * and the one whose canonical-address binding (#682) makes the buttons answer
840
+ * in a Telegram General topic.
841
+ *
842
+ * Unlike the login chooser this does NOT filter to backends the fleet already
843
+ * runs. Installing is how you get a backend you do not have yet, so filtering
844
+ * by configured backends would hide the only entry the admin came for.
845
+ *
846
+ * gemini-cli is omitted: it is deprecated (see backend/factory.ts). Typing
847
+ * `/install-cli gemini-cli` still works — this only stops recommending it.
848
+ */
849
+ promptInstallBackends(chat: {
850
+ adapter: ChannelAdapter;
851
+ adapterId: string;
852
+ chatId: string;
853
+ threadId?: string;
854
+ }): Promise<void>;
855
+ /** Backend chooser button → start that backend's install session. */
856
+ private handleInstallBackendSelect;
835
857
  /**
836
858
  * Start a login session for one backend. Caller enforces admin.
837
859
  * Returns a status line to post, or null when a confirmation prompt was
@@ -216,6 +216,7 @@ const CLEAR_CONFIRM_CALLBACK_PREFIX = "clear-confirm:";
216
216
  const TIP_DISMISS_CALLBACK_PREFIX = "tip-dismiss:";
217
217
  const TIP_UNLOCK_CALLBACK_PREFIX = "tip-unlock:";
218
218
  const LOGIN_CALLBACK_PREFIX = "login:";
219
+ const INSTALL_CALLBACK_PREFIX = "install-select:";
219
220
  const LOGIN_MENU_CALLBACK_PREFIX = "login-menu:";
220
221
  const LOGIN_CONFIRM_CALLBACK_PREFIX = "login-confirm:";
221
222
  const INSTALL_LOGIN_CALLBACK_PREFIX = "install-login:";
@@ -2419,6 +2420,8 @@ export class FleetManager {
2419
2420
  return;
2420
2421
  if (await this.handleLoginBackendSelect(data, adapterId, this.adapter ?? undefined))
2421
2422
  return;
2423
+ if (await this.handleInstallBackendSelect(data, adapterId, this.adapter ?? undefined))
2424
+ return;
2422
2425
  if (await this.handleLoginMenuSelect(data, adapterId, this.adapter ?? undefined))
2423
2426
  return;
2424
2427
  if (await this.handleLoginConfirm(data, adapterId, this.adapter ?? undefined))
@@ -2813,6 +2816,8 @@ export class FleetManager {
2813
2816
  return;
2814
2817
  if (await this.handleLoginBackendSelect(data, adapterId, adapter))
2815
2818
  return;
2819
+ if (await this.handleInstallBackendSelect(data, adapterId, adapter))
2820
+ return;
2816
2821
  if (await this.handleLoginMenuSelect(data, adapterId, adapter))
2817
2822
  return;
2818
2823
  if (await this.handleLoginConfirm(data, adapterId, adapter))
@@ -5170,8 +5175,13 @@ export class FleetManager {
5170
5175
  */
5171
5176
  async postNonceButtonPrompt(opts) {
5172
5177
  // 16 bytes = the 128-bit capability the design claims. Telegram's 64-byte
5173
- // callback_data cap still holds: longest id is "interactive-assist:" (19)
5174
- // + 32 hex + ":confirm" (8) = 59 bytes.
5178
+ // callback_data cap still holds, with two prefixes tied at the longest:
5179
+ // "interactive-assist:" (19) + 32 hex + ":confirm" (8) = 59, and
5180
+ // "install-select:" (15) + 32 hex + ":" + the longest backend name
5181
+ // ("claude-code"/"antigravity", 11) = 59. Only 5 bytes of headroom: a
5182
+ // backend name of 17+ characters, or a longer prefix, would be silently
5183
+ // rejected by Telegram — see the callback_data assertion in
5184
+ // install-backend-menu.test.ts.
5175
5185
  const nonce = randomBytes(16).toString("hex");
5176
5186
  const entry = {
5177
5187
  prefix: opts.prefix,
@@ -6410,6 +6420,56 @@ export class FleetManager {
6410
6420
  expiredText: t("buttons.stale"),
6411
6421
  });
6412
6422
  }
6423
+ /**
6424
+ * Backend chooser for a bare `/install-cli`, mirroring promptLoginBackends so
6425
+ * both commands feel the same. Built on postNonceButtonPrompt rather than the
6426
+ * `/model` selection coordinator: that is the mechanism `/login` already uses,
6427
+ * and the one whose canonical-address binding (#682) makes the buttons answer
6428
+ * in a Telegram General topic.
6429
+ *
6430
+ * Unlike the login chooser this does NOT filter to backends the fleet already
6431
+ * runs. Installing is how you get a backend you do not have yet, so filtering
6432
+ * by configured backends would hide the only entry the admin came for.
6433
+ *
6434
+ * gemini-cli is omitted: it is deprecated (see backend/factory.ts). Typing
6435
+ * `/install-cli gemini-cli` still works — this only stops recommending it.
6436
+ */
6437
+ async promptInstallBackends(chat) {
6438
+ const choices = Object.keys(BACKEND_INSTALLATION_INFO)
6439
+ .filter(backend => backend !== "gemini-cli")
6440
+ .map(backend => ({ action: backend, label: backend }));
6441
+ await this.postNonceButtonPrompt({
6442
+ prefix: INSTALL_CALLBACK_PREFIX,
6443
+ alertType: "install",
6444
+ instanceName: "install",
6445
+ adapter: chat.adapter,
6446
+ adapterId: chat.adapterId,
6447
+ chatId: chat.chatId,
6448
+ threadId: chat.threadId,
6449
+ message: t("install.choose_backend"),
6450
+ choices,
6451
+ expiredText: t("buttons.stale"),
6452
+ });
6453
+ }
6454
+ /** Backend chooser button → start that backend's install session. */
6455
+ async handleInstallBackendSelect(data, callbackAdapterId, receivingAdapter) {
6456
+ const claimed = this.consumeNonceCallback(INSTALL_CALLBACK_PREFIX, /^install-select:([0-9a-f]+):([a-z][a-z-]*)$/, data, callbackAdapterId, receivingAdapter);
6457
+ if (claimed === null)
6458
+ return false;
6459
+ if (claimed === "consumed")
6460
+ return true;
6461
+ const { entry, action: backend } = claimed;
6462
+ await this.retireNonceButtons(entry, entry.messageId ?? data.messageId, t("install.starting_backend", backend));
6463
+ const text = await this.startInstallSession(backend, {
6464
+ adapter: entry.adapter,
6465
+ adapterId: entry.adapterId,
6466
+ chatId: entry.chatId,
6467
+ threadId: entry.threadId,
6468
+ });
6469
+ if (text)
6470
+ await entry.adapter.sendText(entry.chatId, text, { threadId: entry.threadId }).catch(() => { });
6471
+ return true;
6472
+ }
6413
6473
  /**
6414
6474
  * Start a login session for one backend. Caller enforces admin.
6415
6475
  * Returns a status line to post, or null when a confirmation prompt was
@@ -6801,7 +6861,11 @@ export class FleetManager {
6801
6861
  }
6802
6862
  const backend = String(data.options?.backend ?? "").trim();
6803
6863
  if (!backend) {
6804
- await data.respond(t("install.usage"));
6864
+ // Bare call: offer the backends instead of printing a usage line the user
6865
+ // then has to retype. A Discord slash that DID pick the native `backend`
6866
+ // choice never lands here, so the two paths cannot both fire.
6867
+ await this.promptInstallBackends({ adapter, adapterId, chatId: data.channelId });
6868
+ await data.respond(t("install.chooser_posted"));
6805
6869
  return;
6806
6870
  }
6807
6871
  await data.respond(await this.startInstallSession(backend, { adapter, adapterId, chatId: data.channelId }));