@songsid/agend 2.1.4-beta.30 → 2.1.4-beta.32

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 (41) hide show
  1. package/dist/backend/claude-code.d.ts +10 -0
  2. package/dist/backend/claude-code.js +56 -3
  3. package/dist/backend/claude-code.js.map +1 -1
  4. package/dist/backend/codex.js +1 -1
  5. package/dist/backend/codex.js.map +1 -1
  6. package/dist/backend/gemini-cli.js +2 -2
  7. package/dist/backend/gemini-cli.js.map +1 -1
  8. package/dist/backend/grok.js +2 -2
  9. package/dist/backend/grok.js.map +1 -1
  10. package/dist/backend/kiro.d.ts +1 -0
  11. package/dist/backend/kiro.js +10 -3
  12. package/dist/backend/kiro.js.map +1 -1
  13. package/dist/backend/opencode.js +2 -2
  14. package/dist/backend/opencode.js.map +1 -1
  15. package/dist/backend/types.d.ts +14 -0
  16. package/dist/backend/types.js +5 -1
  17. package/dist/backend/types.js.map +1 -1
  18. package/dist/channel/adapters/discord.d.ts +2 -0
  19. package/dist/channel/adapters/discord.js +6 -0
  20. package/dist/channel/adapters/discord.js.map +1 -1
  21. package/dist/channel/adapters/telegram.d.ts +6 -0
  22. package/dist/channel/adapters/telegram.js +10 -0
  23. package/dist/channel/adapters/telegram.js.map +1 -1
  24. package/dist/channel/types.d.ts +2 -0
  25. package/dist/daemon.d.ts +11 -0
  26. package/dist/daemon.js +130 -28
  27. package/dist/daemon.js.map +1 -1
  28. package/dist/fleet-manager.d.ts +4 -0
  29. package/dist/fleet-manager.js +100 -7
  30. package/dist/fleet-manager.js.map +1 -1
  31. package/dist/instance-lifecycle.d.ts +17 -0
  32. package/dist/instance-lifecycle.js +71 -0
  33. package/dist/instance-lifecycle.js.map +1 -1
  34. package/dist/locale.js +6 -0
  35. package/dist/locale.js.map +1 -1
  36. package/dist/topic-commands.js +5 -1
  37. package/dist/topic-commands.js.map +1 -1
  38. package/dist/usage/providers.d.ts +7 -0
  39. package/dist/usage/providers.js +18 -1
  40. package/dist/usage/providers.js.map +1 -1
  41. package/package.json +1 -1
@@ -1021,6 +1021,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1021
1021
  * Returns null on success (menu shown), or a fallback string to send.
1022
1022
  */
1023
1023
  promptModelMenu(instanceName: string, userId: string, channelId: string, adapter: ChannelAdapter, chatId: string, threadId?: string): Promise<string | null>;
1024
+ /** Cached-or-live account catalog behind "/model → more models" (claude). */
1025
+ private claudeApiModelOptions;
1026
+ /** Replace a consumed "/model" menu with the full account catalog tier. */
1027
+ private expandClaudeModelMenu;
1024
1028
  /** Consume a `/model` selection callback. Returns true for all model-select ids (incl. stale). */
1025
1029
  private handleModelSelection;
1026
1030
  /** Apply a model to an instance: runtime paste (claude-code) or persist + restart (others). */
@@ -5109,7 +5109,7 @@ export class FleetManager {
5109
5109
  * longer parses) collapses the clicked message so the dead button stops
5110
5110
  * inviting clicks — the same courtesy the cancel button extends.
5111
5111
  */
5112
- consumeNonceCallback(prefix, actionRe, data, callbackAdapterId, receivingAdapter) {
5112
+ consumeNonceCallback(prefix, actionRe, data, callbackAdapterId, receivingAdapter, staleHandling) {
5113
5113
  if (!data.callbackData.startsWith(prefix))
5114
5114
  return null;
5115
5115
  const match = data.callbackData.match(actionRe);
@@ -5121,7 +5121,17 @@ export class FleetManager {
5121
5121
  pending = undefined;
5122
5122
  if (!match || !pending) {
5123
5123
  const adapter = receivingAdapter ?? this.adapter;
5124
- adapter?.editMessageRemoveButtons?.(data.chatId, data.messageId, t("buttons.stale"), data.threadId).catch(() => { });
5124
+ if (staleHandling?.keepText && adapter?.removeMessageButtons) {
5125
+ adapter.removeMessageButtons(data.chatId, data.messageId, data.threadId)
5126
+ .catch(() => { });
5127
+ }
5128
+ else {
5129
+ adapter?.editMessageRemoveButtons?.(data.chatId, data.messageId, t("buttons.stale"), data.threadId).catch(() => { });
5130
+ }
5131
+ if (staleHandling?.notice) {
5132
+ adapter?.sendText(data.chatId, staleHandling.notice, { threadId: data.threadId })
5133
+ .catch(() => { });
5134
+ }
5125
5135
  return "consumed";
5126
5136
  }
5127
5137
  // Bind the capability to the exact message/world that created it. Telegram
@@ -5294,7 +5304,12 @@ export class FleetManager {
5294
5304
  }
5295
5305
  }
5296
5306
  async handleTipDismiss(data, callbackAdapterId, receivingAdapter) {
5297
- const claimed = this.consumeNonceCallback(TIP_DISMISS_CALLBACK_PREFIX, /^tip-dismiss:([0-9a-f]+):(dismiss|confused)$/, data, callbackAdapterId, receivingAdapter);
5307
+ const claimed = this.consumeNonceCallback(TIP_DISMISS_CALLBACK_PREFIX, /^tip-dismiss:([0-9a-f]+):(dismiss|confused)$/, data, callbackAdapterId, receivingAdapter,
5308
+ // A restart orphans the expiry timer, so tip buttons can outlive their
5309
+ // nonce by days. Keep the tip text readable and always tell the clicker
5310
+ // what to do next (the keyboard edit itself can be refused: Telegram
5311
+ // rejects edits on messages older than 48h).
5312
+ { keepText: true, notice: t("tips.expired_use_tips") });
5298
5313
  if (claimed === null)
5299
5314
  return false;
5300
5315
  if (claimed === "consumed")
@@ -5363,7 +5378,7 @@ export class FleetManager {
5363
5378
  return true;
5364
5379
  }
5365
5380
  async handleTipUnlock(data, callbackAdapterId, receivingAdapter) {
5366
- const claimed = this.consumeNonceCallback(TIP_UNLOCK_CALLBACK_PREFIX, /^tip-unlock:([0-9a-f]+):(unlock)$/, data, callbackAdapterId, receivingAdapter);
5381
+ const claimed = this.consumeNonceCallback(TIP_UNLOCK_CALLBACK_PREFIX, /^tip-unlock:([0-9a-f]+):(unlock)$/, data, callbackAdapterId, receivingAdapter, { keepText: true, notice: t("tips.expired_use_tips") });
5367
5382
  if (claimed === null)
5368
5383
  return false;
5369
5384
  if (claimed === "consumed")
@@ -7504,6 +7519,13 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
7504
7519
  if (previous?.models?.length)
7505
7520
  env.models = previous.models;
7506
7521
  }
7522
+ // Same protection for the extended catalog: one offline moment must not
7523
+ // blank a good account list for the whole cache TTL.
7524
+ if (!env.apiModels?.length) {
7525
+ const previous = this.readCliEnv(backend);
7526
+ if (previous?.apiModels?.length)
7527
+ env.apiModels = previous.apiModels;
7528
+ }
7507
7529
  const path = this.cliEnvPath(backend);
7508
7530
  mkdirSync(dirname(path), { recursive: true });
7509
7531
  writeFileSync(path, JSON.stringify(env, null, 2));
@@ -7839,13 +7861,17 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
7839
7861
  // Raw id for ✓-matching options; display resolves an inherited CLI default.
7840
7862
  const { model: currentModel, display: currentDisplay } = this.resolveInstanceModel(name);
7841
7863
  const nonce = randomBytes(6).toString("hex");
7842
- const choices = options.slice(0, 25).map(o => ({
7864
+ const isClaude = this.backendNameForInstance(name) === "claude-code";
7865
+ const choices = options.slice(0, isClaude ? 24 : 25).map(o => ({
7843
7866
  id: `${MODEL_SELECT_CALLBACK_PREFIX}${nonce}:${o.id}`,
7844
7867
  label: this.modelChoiceLabel(o, currentModel),
7845
7868
  }));
7869
+ if (isClaude) {
7870
+ choices.push({ id: `${MODEL_SELECT_CALLBACK_PREFIX}${nonce}:__more__`, label: t("model.more") });
7871
+ }
7846
7872
  const timer = setTimeout(() => this.pendingModelSelects.delete(nonce), CLASSIC_BACKEND_SELECTION_TIMEOUT_MS);
7847
7873
  timer.unref?.();
7848
- this.pendingModelSelects.set(nonce, { instanceName: name, model: "", userId: data.userId, channelId: data.channelId, timer, respond: data.respond });
7874
+ this.pendingModelSelects.set(nonce, { instanceName: name, model: "", userId: data.userId, channelId: data.channelId, timer, respond: data.respond, respondChoices: data.respondChoices });
7849
7875
  try {
7850
7876
  await data.respondChoices(t("model.menu", `**${currentDisplay}**`), choices);
7851
7877
  }
@@ -7868,10 +7894,15 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
7868
7894
  }
7869
7895
  const { model: currentModel, display: currentDisplay } = this.resolveInstanceModel(instanceName);
7870
7896
  const nonce = randomBytes(6).toString("hex");
7871
- const choices = options.slice(0, 25).map(o => ({
7897
+ const isClaude = this.backendNameForInstance(instanceName) === "claude-code";
7898
+ // Keep the more-models entry inside Discord's 25-option select cap.
7899
+ const choices = options.slice(0, isClaude ? 24 : 25).map(o => ({
7872
7900
  id: `${MODEL_SELECT_CALLBACK_PREFIX}${nonce}:${o.id}`,
7873
7901
  label: this.modelChoiceLabel(o, currentModel),
7874
7902
  }));
7903
+ if (isClaude) {
7904
+ choices.push({ id: `${MODEL_SELECT_CALLBACK_PREFIX}${nonce}:__more__`, label: t("model.more") });
7905
+ }
7875
7906
  const respond = async (text) => {
7876
7907
  await adapter.sendText(chatId, text, { threadId });
7877
7908
  return undefined;
@@ -7899,6 +7930,62 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
7899
7930
  return t("model.usage");
7900
7931
  }
7901
7932
  }
7933
+ /** Cached-or-live account catalog behind "/model → more models" (claude). */
7934
+ async claudeApiModelOptions() {
7935
+ const cached = this.readCliEnv("claude-code");
7936
+ if (cached?.apiModels?.length)
7937
+ return cached.apiModels;
7938
+ const env = await this.probeBackend("claude-code");
7939
+ return env?.apiModels ?? [];
7940
+ }
7941
+ /** Replace a consumed "/model" menu with the full account catalog tier. */
7942
+ async expandClaudeModelMenu(pending) {
7943
+ const expanded = await this.claudeApiModelOptions();
7944
+ if (!expanded.length) {
7945
+ await pending.respond(t("model.more_unavailable")).catch(() => { });
7946
+ return;
7947
+ }
7948
+ const { model: currentModel, display: currentDisplay } = this.resolveInstanceModel(pending.instanceName);
7949
+ const nonce = randomBytes(6).toString("hex");
7950
+ const choices = expanded.slice(0, 25).map(o => ({
7951
+ id: `${MODEL_SELECT_CALLBACK_PREFIX}${nonce}:${o.id}`,
7952
+ label: this.modelChoiceLabel(o, currentModel),
7953
+ }));
7954
+ const timer = setTimeout(() => {
7955
+ const p = this.pendingModelSelects.get(nonce);
7956
+ if (p) {
7957
+ this.pendingModelSelects.delete(nonce);
7958
+ p.respond(t("model.selection_expired")).catch(() => { });
7959
+ }
7960
+ }, CLASSIC_BACKEND_SELECTION_TIMEOUT_MS);
7961
+ timer.unref?.();
7962
+ this.pendingModelSelects.set(nonce, { ...pending, model: "", timer });
7963
+ try {
7964
+ if (pending.respondChoices) {
7965
+ // Discord select menu: edit the same interaction reply in place.
7966
+ await pending.respondChoices(t("model.menu", `**${currentDisplay}**`), choices);
7967
+ return;
7968
+ }
7969
+ if (pending.adapter && pending.adapterChatId) {
7970
+ // Telegram: retire the tier-1 keyboard, then post the expanded menu.
7971
+ if (pending.menuMessageId && pending.adapter.editMessageRemoveButtons) {
7972
+ await pending.adapter.editMessageRemoveButtons(pending.adapterChatId, pending.menuMessageId, t("model.more"), pending.adapterThreadId).catch(() => { });
7973
+ }
7974
+ const menuMessageId = await pending.adapter.promptUser(pending.adapterChatId, t("model.menu", currentDisplay), choices, { threadId: pending.adapterThreadId });
7975
+ const fresh = this.pendingModelSelects.get(nonce);
7976
+ if (fresh)
7977
+ fresh.menuMessageId = menuMessageId;
7978
+ return;
7979
+ }
7980
+ await pending.respond(t("model.more_unavailable")).catch(() => { });
7981
+ }
7982
+ catch (err) {
7983
+ this.pendingModelSelects.delete(nonce);
7984
+ clearTimeout(timer);
7985
+ this.logger.warn({ err, instanceName: pending.instanceName }, "Expanded model menu failed");
7986
+ await pending.respond(t("model.more_unavailable")).catch(() => { });
7987
+ }
7988
+ }
7902
7989
  /** Consume a `/model` selection callback. Returns true for all model-select ids (incl. stale). */
7903
7990
  async handleModelSelection(data) {
7904
7991
  if (!data.callbackData.startsWith(MODEL_SELECT_CALLBACK_PREFIX))
@@ -7918,6 +8005,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
7918
8005
  this.pendingModelSelects.delete(match[1]);
7919
8006
  clearTimeout(pending.timer);
7920
8007
  const model = match[2];
8008
+ // "More models…" is a navigation choice, not a model: swap the menu for the
8009
+ // full account catalog (live /v1/models with [1m] variants).
8010
+ if (model === "__more__") {
8011
+ await this.expandClaudeModelMenu(pending);
8012
+ return true;
8013
+ }
7921
8014
  // Send immediate "⏳ Switching..." feedback, then apply in background.
7922
8015
  const progressText = t("model.switching", pending.instanceName, model);
7923
8016
  let progressMsgId;