talon-agent 3.15.1 → 3.15.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talon-agent",
3
- "version": "3.15.1",
3
+ "version": "3.15.2",
4
4
  "description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
5
5
  "author": "Dylan Neve",
6
6
  "license": "MIT",
@@ -70,7 +70,9 @@ export async function handleAdminCommand(
70
70
  const model = formatModelLabel(
71
71
  getChatSettings(s.chatId).model ?? config.model,
72
72
  );
73
- return `<b>${escapeHtml(title)}</b> <code>${s.chatId}</code>\n ${s.info.turns} turns | ${age} | ${model}`;
73
+ // `model` is a catalog id (OpenRouter/Kilo ids are free-form), so
74
+ // it gets the same escaping the title already had.
75
+ return `<b>${escapeHtml(title)}</b> <code>${s.chatId}</code>\n ${s.info.turns} turns | ${age} | ${escapeHtml(model)}`;
74
76
  });
75
77
  await ctx.reply(
76
78
  `<b>Active chats (${sessions.length})</b>\n\n` + lines.join("\n\n"),
@@ -132,11 +132,17 @@ export function registerInfoCommands(bot: Bot): void {
132
132
  return;
133
133
  }
134
134
  const lines = plugins.map((p) => {
135
- const ver = p.plugin.version ? ` v${p.plugin.version}` : "";
136
- const desc = p.plugin.description ? ` — ${p.plugin.description}` : "";
135
+ // Every field here is author-supplied manifest text, not just the
136
+ // name. A description like "R&D tools" or "<beta>" would otherwise
137
+ // reach Telegram as markup and 400 the whole listing, so `/plugins`
138
+ // would look dead rather than show one odd line.
139
+ const ver = p.plugin.version ? ` v${escapeHtml(p.plugin.version)}` : "";
140
+ const desc = p.plugin.description
141
+ ? ` — ${escapeHtml(p.plugin.description)}`
142
+ : "";
137
143
  const mcp = p.plugin.mcpServerPath ? " [MCP]" : "";
138
144
  const fe = p.plugin.frontends?.length
139
- ? ` (${p.plugin.frontends.join(", ")})`
145
+ ? ` (${escapeHtml(p.plugin.frontends.join(", "))})`
140
146
  : "";
141
147
  return `• <b>${escapeHtml(p.plugin.name)}</b>${ver}${mcp}${fe}${desc}`;
142
148
  });
@@ -122,10 +122,16 @@ async function flushQueue(chatId: string): Promise<void> {
122
122
  // Clear hourglass reactions on queued messages now that we're processing
123
123
  for (const msgId of queuedReactionMsgIds) {
124
124
  bot.api.setMessageReaction(numericChatId, msgId, []).catch((err) => {
125
- logWarn(
126
- "bot",
127
- `Failed to clear reaction on msg ${msgId}: ${err instanceof Error ? err.message : err}`,
128
- );
125
+ const detail = err instanceof Error ? err.message : String(err);
126
+ // The id is recorded optimistically — the hourglass `setMessageReaction`
127
+ // above is fire-and-forget, so we queue the clear before knowing the set
128
+ // landed. When it didn't, clearing a reaction that was never there comes
129
+ // back as REACTION_EMPTY. The end state we wanted (no reaction) already
130
+ // holds, so that is a no-op, not a failure. Keeping the optimistic push
131
+ // matters: gating it on the set resolving would race the flush and strand
132
+ // a ⏳ on the message. Anything else is still worth surfacing.
133
+ if (detail.includes("REACTION_EMPTY")) return;
134
+ logWarn("bot", `Failed to clear reaction on msg ${msgId}: ${detail}`);
129
135
  });
130
136
  }
131
137