@songsid/agend 2.1.0-beta.44 → 2.1.0-beta.46

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.
@@ -75,4 +75,10 @@ export interface FleetContext {
75
75
  lastError?: string;
76
76
  }>;
77
77
  getInstanceExecutionState?(name: string): "idle" | "working" | "stuck" | "paused" | null;
78
+ /**
79
+ * Show a model-selection inline keyboard for the given instance in a TG topic.
80
+ * Returns a fallback text message if no model list is available (caller should send it).
81
+ * Returns null if the menu was shown successfully.
82
+ */
83
+ promptModelMenu?(instanceName: string, userId: string, channelId: string, adapter: import("./channel/types.js").ChannelAdapter, chatId: string, threadId?: string): Promise<string | null>;
78
84
  }
@@ -379,6 +379,12 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
379
379
  private getModelOptions;
380
380
  /** `/model` slash handler (admin only). No arg → DC menu; `/model <name>` → apply directly. */
381
381
  private handleModelSlash;
382
+ /**
383
+ * Show a TG inline-keyboard model-selection menu. Reuses the same
384
+ * pendingModelSelects coordinator as the DC Select Menu path.
385
+ * Returns null on success (menu shown), or a fallback string to send.
386
+ */
387
+ promptModelMenu(instanceName: string, userId: string, channelId: string, adapter: ChannelAdapter, chatId: string, threadId?: string): Promise<string | null>;
382
388
  /** Consume a `/model` selection callback. Returns true for all model-select ids (incl. stale). */
383
389
  private handleModelSelection;
384
390
  /** Apply a model to an instance: runtime paste (claude-code) or persist + restart (others). */
@@ -4317,6 +4317,45 @@ When users create specialized instances, suggest these configurations:
4317
4317
  await data.respond("Usage: /model <name> — e.g. /model sonnet");
4318
4318
  }
4319
4319
  }
4320
+ /**
4321
+ * Show a TG inline-keyboard model-selection menu. Reuses the same
4322
+ * pendingModelSelects coordinator as the DC Select Menu path.
4323
+ * Returns null on success (menu shown), or a fallback string to send.
4324
+ */
4325
+ async promptModelMenu(instanceName, userId, channelId, adapter, chatId, threadId) {
4326
+ const options = await this.getModelOptions(instanceName);
4327
+ if (options.length === 0) {
4328
+ return `No model list available for ${instanceName}. Use \`/model <name>\` to set one directly.`;
4329
+ }
4330
+ const nonce = randomBytes(6).toString("hex");
4331
+ const choices = options.slice(0, 25).map(o => ({
4332
+ id: `${MODEL_SELECT_CALLBACK_PREFIX}${nonce}:${o.id}`,
4333
+ label: o.description ? `${o.label} — ${o.description}` : o.label,
4334
+ }));
4335
+ const respond = async (text) => {
4336
+ await adapter.sendText(chatId, text, { threadId });
4337
+ return undefined;
4338
+ };
4339
+ const timer = setTimeout(() => {
4340
+ const p = this.pendingModelSelects.get(nonce);
4341
+ if (p) {
4342
+ this.pendingModelSelects.delete(nonce);
4343
+ p.respond("⏰ Model selection expired.").catch(() => { });
4344
+ }
4345
+ }, CLASSIC_BACKEND_SELECTION_TIMEOUT_MS);
4346
+ timer.unref?.();
4347
+ this.pendingModelSelects.set(nonce, { instanceName, model: "", userId, channelId, timer, respond, adapter, adapterChatId: chatId, adapterThreadId: threadId });
4348
+ try {
4349
+ await adapter.promptUser(chatId, `Choose a model for ${instanceName}:`, choices, { threadId });
4350
+ return null; // menu shown
4351
+ }
4352
+ catch (err) {
4353
+ this.pendingModelSelects.delete(nonce);
4354
+ clearTimeout(timer);
4355
+ this.logger.warn({ err, instanceName }, "TG model menu failed");
4356
+ return `Usage: /model <name> — e.g. /model sonnet`;
4357
+ }
4358
+ }
4320
4359
  /** Consume a `/model` selection callback. Returns true for all model-select ids (incl. stale). */
4321
4360
  async handleModelSelection(data) {
4322
4361
  if (!data.callbackData.startsWith(MODEL_SELECT_CALLBACK_PREFIX))
@@ -4335,8 +4374,39 @@ When users create specialized instances, suggest these configurations:
4335
4374
  return true;
4336
4375
  this.pendingModelSelects.delete(match[1]);
4337
4376
  clearTimeout(pending.timer);
4338
- const result = await this.applyModel(pending.instanceName, match[2]);
4339
- await pending.respond(result).catch(() => { });
4377
+ const model = match[2];
4378
+ // Send immediate "⏳ Switching..." feedback, then apply in background.
4379
+ const progressText = `⏳ Switching ${pending.instanceName} to \`${model}\`…`;
4380
+ let progressMsgId;
4381
+ if (pending.adapter && pending.adapterChatId) {
4382
+ // TG path: send a new message and capture messageId for later edit
4383
+ try {
4384
+ const sent = await pending.adapter.sendText(pending.adapterChatId, progressText, { threadId: pending.adapterThreadId });
4385
+ progressMsgId = sent.messageId;
4386
+ }
4387
+ catch { /* non-fatal */ }
4388
+ }
4389
+ else {
4390
+ // DC path: respond immediately with progress text
4391
+ await pending.respond(progressText).catch(() => { });
4392
+ }
4393
+ // Apply model in background — don't await here (keeps callback handler fast)
4394
+ void (async () => {
4395
+ const result = await this.applyModel(pending.instanceName, model);
4396
+ if (pending.adapter && pending.adapterChatId) {
4397
+ if (progressMsgId) {
4398
+ pending.adapter.editMessage(pending.adapterChatId, progressMsgId, result, pending.adapterThreadId).catch(() => {
4399
+ pending.adapter.sendText(pending.adapterChatId, result, { threadId: pending.adapterThreadId }).catch(() => { });
4400
+ });
4401
+ }
4402
+ else {
4403
+ pending.adapter.sendText(pending.adapterChatId, result, { threadId: pending.adapterThreadId }).catch(() => { });
4404
+ }
4405
+ }
4406
+ else {
4407
+ await pending.respond(result).catch(() => { });
4408
+ }
4409
+ })();
4340
4410
  return true;
4341
4411
  }
4342
4412
  /** Apply a model to an instance: runtime paste (claude-code) or persist + restart (others). */