iobroker.pi-agent 0.0.1 → 0.0.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/README.md CHANGED
@@ -101,6 +101,12 @@ Select **DeepSeek** as provider and enter a key from the DeepSeek platform. Avai
101
101
 
102
102
  ## Changelog
103
103
 
104
+ ### 0.0.2 (2026-10-25)
105
+
106
+ - Telegram answers keep their formatting: Markdown of the model is sent as Telegram HTML (bold, italic, code, links;
107
+ tables as a monospace block), and Telegram chats are asked for short answers without tables
108
+ - Released through the CI workflow with npm provenance (trusted publishing)
109
+
104
110
  ### 0.0.1 (2026-10-25)
105
111
 
106
112
  - Initial version: durable agent with the tools `search_objects`, `get_state`, `list_instances` and `set_state`
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "pi-agent",
4
- "version": "0.0.1",
4
+ "version": "0.0.2",
5
5
  "news": {
6
+ "0.0.2": {
7
+ "en": "Telegram answers keep their formatting (bold, italic, code, links); released with npm provenance",
8
+ "de": "Telegram-Antworten behalten ihre Formatierung (fett, kursiv, Code, Links); mit npm-Provenance veröffentlicht",
9
+ "ru": "Ответы в Telegram сохраняют форматирование (жирный, курсив, код, ссылки); опубликовано с npm provenance",
10
+ "pt": "As respostas no Telegram mantêm a formatação (negrito, itálico, código, links); publicado com proveniência npm",
11
+ "nl": "Telegram-antwoorden behouden hun opmaak (vet, cursief, code, links); gepubliceerd met npm-herkomst",
12
+ "fr": "Les réponses Telegram gardent leur mise en forme (gras, italique, code, liens) ; publié avec la provenance npm",
13
+ "it": "Le risposte su Telegram mantengono la formattazione (grassetto, corsivo, codice, link); pubblicato con provenienza npm",
14
+ "es": "Las respuestas en Telegram conservan el formato (negrita, cursiva, código, enlaces); publicado con procedencia npm",
15
+ "pl": "Odpowiedzi w Telegramie zachowują formatowanie (pogrubienie, kursywa, kod, linki); opublikowano z pochodzeniem npm",
16
+ "uk": "Відповіді в Telegram зберігають форматування (жирний, курсив, код, посилання); опубліковано з npm provenance",
17
+ "zh-cn": "Telegram 回答保留格式(粗体、斜体、代码、链接);带 npm 来源证明发布"
18
+ },
6
19
  "0.0.1": {
7
20
  "en": "Initial version: durable agent with search, read and allowlisted write tools; chat via Telegram",
8
21
  "de": "Erste Version: dauerhafter Agent mit Such-, Lese- und Schreibwerkzeugen mit Freigabeliste; Chat über Telegram",
package/lib/telegram.js CHANGED
@@ -120,7 +120,58 @@ function splitMessage(text, max = MAX_MESSAGE) {
120
120
  return parts;
121
121
  }
122
122
 
123
+ /** Instructions for conversations that are Telegram chats. */
124
+ const TELEGRAM_INSTRUCTIONS =
125
+ 'This conversation is a Telegram chat. Keep answers short. Use only simple formatting (bold, italic, inline code, short lists); avoid tables and headings.';
126
+
127
+ /**
128
+ * Escape text for Telegram's HTML parse mode.
129
+ *
130
+ * @param {string} text plain text
131
+ * @returns {string} text with &, < and > escaped
132
+ */
133
+ function escapeHtml(text) {
134
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
135
+ }
136
+
137
+ /**
138
+ * Convert the Markdown a model writes into the small HTML subset Telegram understands (parse_mode "HTML").
139
+ * Everything else is escaped, so a stray "<" in an answer can never break the message.
140
+ *
141
+ * @param {string} markdown answer of the model
142
+ * @returns {string} Telegram HTML
143
+ */
144
+ function markdownToTelegramHtml(markdown) {
145
+ const kept = [];
146
+ // placeholders use a private-use character that does not occur in an answer
147
+ const keep = html => `${kept.push(html) - 1}`;
148
+ let text = String(markdown || '');
149
+ // fenced code blocks and tables keep their layout in a monospace block
150
+ text = text.replace(/```[^\n]*\n([\s\S]*?)```/g, (_, code) =>
151
+ keep(`<pre>${escapeHtml(code.replace(/\n$/, ''))}</pre>`),
152
+ );
153
+ text = text.replace(/(?:^[ \t]*\|.*\|[ \t]*(?:\n|$))+/gm, table => {
154
+ const rows = table
155
+ .trimEnd()
156
+ .split('\n')
157
+ .map(row => row.trim())
158
+ .filter(row => !/^\|[\s:|-]+\|$/.test(row));
159
+ return `${keep(`<pre>${escapeHtml(rows.join('\n'))}</pre>`)}\n`;
160
+ });
161
+ text = escapeHtml(text);
162
+ text = text.replace(/`([^`\n]+)`/g, (_, code) => keep(`<code>${code}</code>`));
163
+ text = text.replace(/^#{1,6}[ \t]+(.+)$/gm, '<b>$1</b>');
164
+ text = text.replace(/^([ \t]*)[-*+][ \t]+/gm, '$1• ');
165
+ text = text.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>');
166
+ text = text.replace(/(^|[^*\w])\*(?!\s)([^*\n]+?)\*(?![*\w])/g, '$1<i>$2</i>');
167
+ text = text.replace(/~~(.+?)~~/g, '<s>$1</s>');
168
+ text = text.replace(/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2">$1</a>');
169
+ return text.replace(/(\d+)/g, (_, i) => kept[Number(i)]);
170
+ }
171
+
123
172
  module.exports = {
173
+ TELEGRAM_INSTRUCTIONS,
174
+ markdownToTelegramHtml,
124
175
  parseAllowedUsers,
125
176
  isAllowedUser,
126
177
  parseRequest,
package/main.js CHANGED
@@ -13,6 +13,8 @@ const {
13
13
  stripPrefix,
14
14
  telegramRequestId,
15
15
  splitMessage,
16
+ markdownToTelegramHtml,
17
+ TELEGRAM_INSTRUCTIONS,
16
18
  } = require('./lib/telegram');
17
19
 
18
20
  /**
@@ -164,7 +166,7 @@ class PiAgent extends utils.Adapter {
164
166
  // Chats keep their own conversation; a model change in the settings reaches them too.
165
167
  for (const id of Object.values(this.chats)) {
166
168
  const conversation = await this.harness.conversation(id, this.context);
167
- await conversation?.configure({ model: this.model }, this.context);
169
+ await conversation?.configure({ model: this.model, instructions: TELEGRAM_INSTRUCTIONS }, this.context);
168
170
  }
169
171
  await this.subscribeForeignStatesAsync(`${this.telegram}.communicate.request`);
170
172
  const prefix = String(this.config.telegramPrefix || '').trim();
@@ -191,7 +193,7 @@ class PiAgent extends utils.Adapter {
191
193
  }
192
194
  }
193
195
  const created = await this.harness.createConversation(
194
- { ownership: { kind: 'ownerless' }, agent: { model: this.model } },
196
+ { ownership: { kind: 'ownerless' }, agent: { model: this.model, instructions: TELEGRAM_INSTRUCTIONS } },
195
197
  this.context,
196
198
  );
197
199
  this.chats[target.chatId] = created.id;
@@ -314,7 +316,12 @@ class PiAgent extends utils.Adapter {
314
316
  async deliver(target, text) {
315
317
  if (target.kind === 'telegram') {
316
318
  for (const part of splitMessage(text)) {
317
- this.sendTo(this.telegram, 'send', { text: part, chatId: target.chatId });
319
+ // Telegram shows Markdown literally; its HTML mode renders bold, italic, code and links
320
+ this.sendTo(this.telegram, 'send', {
321
+ text: markdownToTelegramHtml(part),
322
+ chatId: target.chatId,
323
+ parse_mode: 'HTML',
324
+ });
318
325
  }
319
326
  return;
320
327
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.pi-agent",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Durable LLM agent for ioBroker: crash-safe tool calls, write allowlist, idempotent triggers.",
5
5
  "author": {
6
6
  "name": "Alex",